mirror of
https://github.com/hanzoai/esbuild.git
synced 2026-10-30 14:46:14 +00:00
3093 lines
71 KiB
Go
3093 lines
71 KiB
Go
package bundler_tests
|
|
|
|
import (
|
|
"regexp"
|
|
"testing"
|
|
|
|
"github.com/evanw/esbuild/internal/compat"
|
|
"github.com/evanw/esbuild/internal/config"
|
|
)
|
|
|
|
var ts_suite = suite{
|
|
name: "ts",
|
|
}
|
|
|
|
func TestTSDeclareConst(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
declare const require: any
|
|
declare const exports: any;
|
|
declare const module: any
|
|
|
|
declare const foo: any
|
|
let foo = bar()
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSDeclareLet(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
declare let require: any
|
|
declare let exports: any;
|
|
declare let module: any
|
|
|
|
declare let foo: any
|
|
let foo = bar()
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSDeclareVar(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
declare var require: any
|
|
declare var exports: any;
|
|
declare var module: any
|
|
|
|
declare var foo: any
|
|
let foo = bar()
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSDeclareClass(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
declare class require {}
|
|
declare class exports {};
|
|
declare class module {}
|
|
|
|
declare class foo {}
|
|
let foo = bar()
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSDeclareClassFields(t *testing.T) {
|
|
// Note: this test uses arrow functions to validate that
|
|
// scopes inside "declare" fields are correctly discarded
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
import './define-false'
|
|
import './define-true'
|
|
`,
|
|
"/define-false/index.ts": `
|
|
class Foo {
|
|
a
|
|
declare b
|
|
[(() => null, c)]
|
|
declare [(() => null, d)]
|
|
|
|
static A
|
|
static declare B
|
|
static [(() => null, C)]
|
|
static declare [(() => null, D)]
|
|
}
|
|
(() => new Foo())()
|
|
`,
|
|
"/define-true/index.ts": `
|
|
class Bar {
|
|
a
|
|
declare b
|
|
[(() => null, c)]
|
|
declare [(() => null, d)]
|
|
|
|
static A
|
|
static declare B
|
|
static [(() => null, C)]
|
|
static declare [(() => null, D)]
|
|
}
|
|
(() => new Bar())()
|
|
`,
|
|
"/define-false/tsconfig.json": `{
|
|
"compilerOptions": {
|
|
"useDefineForClassFields": false
|
|
}
|
|
}`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
UnsupportedJSFeatures: compat.ClassField,
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSDeclareFunction(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
declare function require(): void
|
|
declare function exports(): void;
|
|
declare function module(): void
|
|
|
|
declare function foo() {}
|
|
let foo = bar()
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSDeclareNamespace(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
declare namespace require {}
|
|
declare namespace exports {};
|
|
declare namespace module {}
|
|
|
|
declare namespace foo {}
|
|
let foo = bar()
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSDeclareEnum(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
declare enum require {}
|
|
declare enum exports {};
|
|
declare enum module {}
|
|
|
|
declare enum foo {}
|
|
let foo = bar()
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSDeclareConstEnum(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
declare const enum require {}
|
|
declare const enum exports {};
|
|
declare const enum module {}
|
|
|
|
declare const enum foo {}
|
|
let foo = bar()
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSConstEnumComments(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/bar.ts": `
|
|
export const enum Foo {
|
|
"%/*" = 1,
|
|
"*/%" = 2,
|
|
}
|
|
`,
|
|
"/foo.ts": `
|
|
import { Foo } from "./bar";
|
|
const enum Bar {
|
|
"%/*" = 1,
|
|
"*/%" = 2,
|
|
}
|
|
console.log({
|
|
'should have comments': [
|
|
Foo["%/*"],
|
|
Bar["%/*"],
|
|
],
|
|
'should not have comments': [
|
|
Foo["*/%"],
|
|
Bar["*/%"],
|
|
],
|
|
});
|
|
`,
|
|
},
|
|
entryPaths: []string{"/foo.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSImportEmptyNamespace(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
import {ns} from './ns.ts'
|
|
function foo(): ns.type {}
|
|
foo();
|
|
`,
|
|
"/ns.ts": `
|
|
export namespace ns {}
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSImportMissingES6(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
import fn, {x as a, y as b} from './foo'
|
|
console.log(fn(a, b))
|
|
`,
|
|
"/foo.js": `
|
|
export const x = 123
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
},
|
|
expectedCompileLog: `entry.ts: ERROR: No matching export in "foo.js" for import "default"
|
|
entry.ts: ERROR: No matching export in "foo.js" for import "y"
|
|
`,
|
|
})
|
|
}
|
|
|
|
func TestTSImportMissingUnusedES6(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
import fn, {x as a, y as b} from './foo'
|
|
`,
|
|
"/foo.js": `
|
|
export const x = 123
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSExportMissingES6(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.js": `
|
|
import * as ns from './foo'
|
|
console.log(ns)
|
|
`,
|
|
"/foo.ts": `
|
|
export {nope} from './bar'
|
|
`,
|
|
"/bar.js": `
|
|
export const yep = 123
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.js"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
},
|
|
})
|
|
}
|
|
|
|
// It's an error to import from a file that does not exist
|
|
func TestTSImportMissingFile(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
import {Something} from './doesNotExist.ts'
|
|
let foo = new Something
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
},
|
|
expectedScanLog: `entry.ts: ERROR: Could not resolve "./doesNotExist.ts"
|
|
`,
|
|
})
|
|
}
|
|
|
|
// It's not an error to import a type from a file that does not exist
|
|
func TestTSImportTypeOnlyFile(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
import {SomeType1} from './doesNotExist1.ts'
|
|
import {SomeType2} from './doesNotExist2.ts'
|
|
let foo: SomeType1 = bar()
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSExportEquals(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/a.ts": `
|
|
import b from './b.ts'
|
|
console.log(b)
|
|
`,
|
|
"/b.ts": `
|
|
export = [123, foo]
|
|
function foo() {}
|
|
`,
|
|
},
|
|
entryPaths: []string{"/a.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSExportNamespace(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/a.ts": `
|
|
import {Foo} from './b.ts'
|
|
console.log(new Foo)
|
|
`,
|
|
"/b.ts": `
|
|
export class Foo {}
|
|
export namespace Foo {
|
|
export let foo = 1
|
|
}
|
|
export namespace Foo {
|
|
export let bar = 2
|
|
}
|
|
`,
|
|
},
|
|
entryPaths: []string{"/a.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSNamespaceKeepNames(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
namespace ns {
|
|
export let foo = () => {}
|
|
export function bar() {}
|
|
export class Baz {}
|
|
}
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
KeepNames: true,
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSNamespaceKeepNamesTargetES2015(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
namespace ns {
|
|
export let foo = () => {}
|
|
export function bar() {}
|
|
export class Baz {}
|
|
}
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
KeepNames: true,
|
|
UnsupportedJSFeatures: es(2015),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSMinifyEnum(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/a.ts": `
|
|
enum Foo { A, B, C = Foo }
|
|
`,
|
|
"/b.ts": `
|
|
export enum Foo { X, Y, Z = Foo }
|
|
`,
|
|
},
|
|
entryPaths: []string{"/a.ts", "/b.ts"},
|
|
options: config.Options{
|
|
MinifySyntax: true,
|
|
MinifyWhitespace: true,
|
|
MinifyIdentifiers: true,
|
|
AbsOutputDir: "/",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSMinifyNestedEnum(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/a.ts": `
|
|
function foo() { enum Foo { A, B, C = Foo } return Foo }
|
|
`,
|
|
"/b.ts": `
|
|
export function foo() { enum Foo { X, Y, Z = Foo } return Foo }
|
|
`,
|
|
},
|
|
entryPaths: []string{"/a.ts", "/b.ts"},
|
|
options: config.Options{
|
|
MinifySyntax: true,
|
|
MinifyWhitespace: true,
|
|
MinifyIdentifiers: true,
|
|
AbsOutputDir: "/",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSMinifyNestedEnumNoLogicalAssignment(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/a.ts": `
|
|
function foo() { enum Foo { A, B, C = Foo } return Foo }
|
|
`,
|
|
"/b.ts": `
|
|
export function foo() { enum Foo { X, Y, Z = Foo } return Foo }
|
|
`,
|
|
},
|
|
entryPaths: []string{"/a.ts", "/b.ts"},
|
|
options: config.Options{
|
|
MinifySyntax: true,
|
|
MinifyWhitespace: true,
|
|
MinifyIdentifiers: true,
|
|
AbsOutputDir: "/",
|
|
UnsupportedJSFeatures: compat.LogicalAssignment,
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSMinifyNestedEnumNoArrow(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/a.ts": `
|
|
function foo() { enum Foo { A, B, C = Foo } return Foo }
|
|
`,
|
|
"/b.ts": `
|
|
export function foo() { enum Foo { X, Y, Z = Foo } return Foo }
|
|
`,
|
|
},
|
|
entryPaths: []string{"/a.ts", "/b.ts"},
|
|
options: config.Options{
|
|
MinifySyntax: true,
|
|
MinifyWhitespace: true,
|
|
MinifyIdentifiers: true,
|
|
AbsOutputDir: "/",
|
|
UnsupportedJSFeatures: compat.Arrow,
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSMinifyNamespace(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/a.ts": `
|
|
namespace Foo {
|
|
export namespace Bar {
|
|
foo(Foo, Bar)
|
|
}
|
|
}
|
|
`,
|
|
"/b.ts": `
|
|
export namespace Foo {
|
|
export namespace Bar {
|
|
foo(Foo, Bar)
|
|
}
|
|
}
|
|
`,
|
|
},
|
|
entryPaths: []string{"/a.ts", "/b.ts"},
|
|
options: config.Options{
|
|
MinifySyntax: true,
|
|
MinifyWhitespace: true,
|
|
MinifyIdentifiers: true,
|
|
AbsOutputDir: "/",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSMinifyNamespaceNoLogicalAssignment(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/a.ts": `
|
|
namespace Foo {
|
|
export namespace Bar {
|
|
foo(Foo, Bar)
|
|
}
|
|
}
|
|
`,
|
|
"/b.ts": `
|
|
export namespace Foo {
|
|
export namespace Bar {
|
|
foo(Foo, Bar)
|
|
}
|
|
}
|
|
`,
|
|
},
|
|
entryPaths: []string{"/a.ts", "/b.ts"},
|
|
options: config.Options{
|
|
MinifySyntax: true,
|
|
MinifyWhitespace: true,
|
|
MinifyIdentifiers: true,
|
|
AbsOutputDir: "/",
|
|
UnsupportedJSFeatures: compat.LogicalAssignment,
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSMinifyNamespaceNoArrow(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/a.ts": `
|
|
namespace Foo {
|
|
export namespace Bar {
|
|
foo(Foo, Bar)
|
|
}
|
|
}
|
|
`,
|
|
"/b.ts": `
|
|
export namespace Foo {
|
|
export namespace Bar {
|
|
foo(Foo, Bar)
|
|
}
|
|
}
|
|
`,
|
|
},
|
|
entryPaths: []string{"/a.ts", "/b.ts"},
|
|
options: config.Options{
|
|
MinifySyntax: true,
|
|
MinifyWhitespace: true,
|
|
MinifyIdentifiers: true,
|
|
AbsOutputDir: "/",
|
|
UnsupportedJSFeatures: compat.Arrow,
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSMinifyDerivedClass(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
class Foo extends Bar {
|
|
foo = 1;
|
|
bar = 2;
|
|
constructor() {
|
|
super();
|
|
foo();
|
|
bar();
|
|
}
|
|
}
|
|
`,
|
|
"/tsconfig.json": `{
|
|
"compilerOptions": {
|
|
"useDefineForClassFields": false
|
|
}
|
|
}`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
MinifySyntax: true,
|
|
UnsupportedJSFeatures: es(2015),
|
|
AbsOutputFile: "/out.js",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSMinifyEnumPropertyNames(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
import { CrossFileGood, CrossFileBad } from './cross-file'
|
|
const enum SameFileGood {
|
|
STR = 'str 1',
|
|
NUM = 123,
|
|
}
|
|
const enum SameFileBad {
|
|
PROTO = '__proto__',
|
|
CONSTRUCTOR = 'constructor',
|
|
PROTOTYPE = 'prototype',
|
|
}
|
|
class Foo {
|
|
[100] = 100;
|
|
'200' = 200;
|
|
['300'] = 300;
|
|
[SameFileGood.STR] = SameFileGood.STR;
|
|
[SameFileGood.NUM] = SameFileGood.NUM;
|
|
[CrossFileGood.STR] = CrossFileGood.STR;
|
|
[CrossFileGood.NUM] = CrossFileGood.NUM;
|
|
}
|
|
shouldNotBeComputed(
|
|
class {
|
|
[100] = 100;
|
|
'200' = 200;
|
|
['300'] = 300;
|
|
[SameFileGood.STR] = SameFileGood.STR;
|
|
[SameFileGood.NUM] = SameFileGood.NUM;
|
|
[CrossFileGood.STR] = CrossFileGood.STR;
|
|
[CrossFileGood.NUM] = CrossFileGood.NUM;
|
|
},
|
|
{
|
|
[100]: 100,
|
|
'200': 200,
|
|
['300']: 300,
|
|
[SameFileGood.STR]: SameFileGood.STR,
|
|
[SameFileGood.NUM]: SameFileGood.NUM,
|
|
[CrossFileGood.STR]: CrossFileGood.STR,
|
|
[CrossFileGood.NUM]: CrossFileGood.NUM,
|
|
},
|
|
)
|
|
mustBeComputed(
|
|
{ [SameFileBad.PROTO]: null },
|
|
{ [CrossFileBad.PROTO]: null },
|
|
class { [SameFileBad.CONSTRUCTOR]() {} },
|
|
class { [CrossFileBad.CONSTRUCTOR]() {} },
|
|
class { static [SameFileBad.PROTOTYPE]() {} },
|
|
class { static [CrossFileBad.PROTOTYPE]() {} },
|
|
)
|
|
`,
|
|
"/cross-file.ts": `
|
|
export const enum CrossFileGood {
|
|
STR = 'str 2',
|
|
NUM = 321,
|
|
}
|
|
export const enum CrossFileBad {
|
|
PROTO = '__proto__',
|
|
CONSTRUCTOR = 'constructor',
|
|
PROTOTYPE = 'prototype',
|
|
}
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
MinifySyntax: true,
|
|
AbsOutputFile: "/out.js",
|
|
},
|
|
})
|
|
}
|
|
func TestTSMinifyEnumCrossFileInlineStringsIntoTemplates(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
import { CrossFile } from './cross-file'
|
|
enum SameFile {
|
|
STR = 'str 1',
|
|
NUM = 123,
|
|
}
|
|
console.log(` + "`" + `
|
|
SameFile.STR = ${SameFile.STR}
|
|
SameFile.NUM = ${SameFile.NUM}
|
|
CrossFile.STR = ${CrossFile.STR}
|
|
CrossFile.NUM = ${CrossFile.NUM}
|
|
` + "`" + `)
|
|
`,
|
|
"/cross-file.ts": `
|
|
export enum CrossFile {
|
|
STR = 'str 2',
|
|
NUM = 321,
|
|
}
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
MinifySyntax: true,
|
|
AbsOutputFile: "/out.js",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSImportVsLocalCollisionAllTypes(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
import {a, b, c, d, e} from './other.ts'
|
|
let a
|
|
const b = 0
|
|
var c
|
|
function d() {}
|
|
class e {}
|
|
console.log(a, b, c, d, e)
|
|
`,
|
|
"/other.ts": `
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSImportVsLocalCollisionMixed(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
import {a, b, c, d, e, real} from './other.ts'
|
|
let a
|
|
const b = 0
|
|
var c
|
|
function d() {}
|
|
class e {}
|
|
console.log(a, b, c, d, e, real)
|
|
`,
|
|
"/other.ts": `
|
|
export let real = 123
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSImportEqualsEliminationTest(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
import a = foo.a
|
|
import b = a.b
|
|
import c = b.c
|
|
|
|
import x = foo.x
|
|
import y = x.y
|
|
import z = y.z
|
|
|
|
export let bar = c
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSImportEqualsTreeShakingFalse(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
import { foo } from 'pkg'
|
|
import used = foo.used
|
|
import unused = foo.unused
|
|
export { used }
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModePassThrough,
|
|
AbsOutputFile: "/out.js",
|
|
TreeShaking: false,
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSImportEqualsTreeShakingTrue(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
import { foo } from 'pkg'
|
|
import used = foo.used
|
|
import unused = foo.unused
|
|
export { used }
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModePassThrough,
|
|
AbsOutputFile: "/out.js",
|
|
TreeShaking: true,
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSImportEqualsBundle(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
import { foo } from 'pkg'
|
|
import used = foo.used
|
|
import unused = foo.unused
|
|
export { used }
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
ExternalSettings: config.ExternalSettings{
|
|
PreResolve: config.ExternalMatchers{
|
|
Exact: map[string]bool{
|
|
"pkg": true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSImportEqualsUndefinedImport(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
import * as ns from './import.ts'
|
|
import value_copy = ns.value
|
|
import Type_copy = ns.Type
|
|
let foo: Type_copy = value_copy
|
|
console.log(foo)
|
|
`,
|
|
"/import.ts": `
|
|
export let value = 123
|
|
export type Type = number
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
ExternalSettings: config.ExternalSettings{
|
|
PreResolve: config.ExternalMatchers{
|
|
Exact: map[string]bool{
|
|
"pkg": true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSMinifiedBundleES6(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
import {foo} from './a'
|
|
console.log(foo())
|
|
`,
|
|
"/a.ts": `
|
|
export function foo() {
|
|
return 123
|
|
}
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
MinifySyntax: true,
|
|
MinifyWhitespace: true,
|
|
MinifyIdentifiers: true,
|
|
AbsOutputFile: "/out.js",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSMinifiedBundleCommonJS(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
const {foo} = require('./a')
|
|
console.log(foo(), require('./j.json'))
|
|
`,
|
|
"/a.ts": `
|
|
exports.foo = function() {
|
|
return 123
|
|
}
|
|
`,
|
|
"/j.json": `
|
|
{"test": true}
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
MinifySyntax: true,
|
|
MinifyWhitespace: true,
|
|
MinifyIdentifiers: true,
|
|
AbsOutputFile: "/out.js",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSExperimentalDecoratorsNoConfig(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
declare let x: any, y: any
|
|
@x.y()
|
|
@(new y.x)
|
|
export default class Foo {
|
|
@x @y mUndef: any
|
|
@x @y mDef = 1
|
|
@x @y method() { return new Foo }
|
|
@x @y accessor aUndef: any
|
|
@x @y accessor aDef = 1
|
|
|
|
@x @y static sUndef: any
|
|
@x @y static sDef = new Foo
|
|
@x @y static sMethod() { return new Foo }
|
|
@x @y static accessor asUndef: any
|
|
@x @y static accessor asDef = 1
|
|
|
|
@x @y #mUndef: any
|
|
@x @y #mDef = 1
|
|
@x @y #method() { return new Foo }
|
|
@x @y accessor #aUndef: any
|
|
@x @y accessor #aDef = 1
|
|
|
|
@x @y static #sUndef: any
|
|
@x @y static #sDef = 1
|
|
@x @y static #sMethod() { return new Foo }
|
|
@x @y static accessor #asUndef: any
|
|
@x @y static accessor #asDef = 1
|
|
}
|
|
`,
|
|
"/tsconfig.json": `{
|
|
"compilerOptions": {
|
|
"experimentalDecorators": false
|
|
}
|
|
}`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSExperimentalDecorators(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.js": `
|
|
import all from './all'
|
|
import all_computed from './all_computed'
|
|
import {a} from './a'
|
|
import {b} from './b'
|
|
import {c} from './c'
|
|
import {d} from './d'
|
|
import e from './e'
|
|
import f from './f'
|
|
import g from './g'
|
|
import h from './h'
|
|
import {i} from './i'
|
|
import {j} from './j'
|
|
import k from './k'
|
|
import {fn} from './arguments'
|
|
console.log(all, all_computed, a, b, c, d, e, f, g, h, i, j, k, fn)
|
|
`,
|
|
"/all.ts": `
|
|
@x.y()
|
|
@new y.x()
|
|
export default class Foo {
|
|
@x @y mUndef
|
|
@x @y mDef = 1
|
|
@x @y method(@x0 @y0 arg0, @x1 @y1 arg1) { return new Foo }
|
|
@x @y declare mDecl
|
|
@x @y abstract mAbst
|
|
constructor(@x0 @y0 arg0, @x1 @y1 arg1) {}
|
|
|
|
@x @y static sUndef
|
|
@x @y static sDef = new Foo
|
|
@x @y static sMethod(@x0 @y0 arg0, @x1 @y1 arg1) { return new Foo }
|
|
@x @y static declare mDecl
|
|
}
|
|
`,
|
|
"/all_computed.ts": `
|
|
@x?.[_ + 'y']()
|
|
@new y()?.[_ + 'x']()
|
|
export default class Foo {
|
|
@x @y [mUndef()]
|
|
@x @y [mDef()] = 1
|
|
@x @y [method()](@x0 @y0 arg0, @x1 @y1 arg1) { return new Foo }
|
|
@x @y declare [mDecl()]
|
|
@x @y abstract [mAbst()]
|
|
|
|
// Side effect order must be preserved even for fields without decorators
|
|
[xUndef()]
|
|
[xDef()] = 2
|
|
static [yUndef()]
|
|
static [yDef()] = 3
|
|
|
|
@x @y static [sUndef()]
|
|
@x @y static [sDef()] = new Foo
|
|
@x @y static [sMethod()](@x0 @y0 arg0, @x1 @y1 arg1) { return new Foo }
|
|
@x @y static declare [mDecl()]
|
|
}
|
|
`,
|
|
"/a.ts": `
|
|
@x(() => 0) @y(() => 1)
|
|
class a_class {
|
|
fn() { return new a_class }
|
|
static z = new a_class
|
|
}
|
|
export let a = a_class
|
|
`,
|
|
"/b.ts": `
|
|
@x(() => 0) @y(() => 1)
|
|
abstract class b_class {
|
|
fn() { return new b_class }
|
|
static z = new b_class
|
|
}
|
|
export let b = b_class
|
|
`,
|
|
"/c.ts": `
|
|
@x(() => 0) @y(() => 1)
|
|
export class c {
|
|
fn() { return new c }
|
|
static z = new c
|
|
}
|
|
`,
|
|
"/d.ts": `
|
|
@x(() => 0) @y(() => 1)
|
|
export abstract class d {
|
|
fn() { return new d }
|
|
static z = new d
|
|
}
|
|
`,
|
|
"/e.ts": `
|
|
@x(() => 0) @y(() => 1)
|
|
export default class {}
|
|
`,
|
|
"/f.ts": `
|
|
@x(() => 0) @y(() => 1)
|
|
export default class f {
|
|
fn() { return new f }
|
|
static z = new f
|
|
}
|
|
`,
|
|
"/g.ts": `
|
|
@x(() => 0) @y(() => 1)
|
|
export default abstract class {}
|
|
`,
|
|
"/h.ts": `
|
|
@x(() => 0) @y(() => 1)
|
|
export default abstract class h {
|
|
fn() { return new h }
|
|
static z = new h
|
|
}
|
|
`,
|
|
"/i.ts": `
|
|
class i_class {
|
|
@x(() => 0) @y(() => 1)
|
|
foo
|
|
}
|
|
export let i = i_class
|
|
`,
|
|
"/j.ts": `
|
|
export class j {
|
|
@x(() => 0) @y(() => 1)
|
|
foo() {}
|
|
}
|
|
`,
|
|
"/k.ts": `
|
|
export default class {
|
|
foo(@x(() => 0) @y(() => 1) x) {}
|
|
}
|
|
`,
|
|
"/arguments.ts": `
|
|
function dec(x: any): any {}
|
|
export function fn(x: string): any {
|
|
class Foo {
|
|
@dec(arguments[0])
|
|
[arguments[0]]() {}
|
|
}
|
|
return Foo;
|
|
}
|
|
`,
|
|
"/tsconfig.json": `{
|
|
"compilerOptions": {
|
|
"useDefineForClassFields": false,
|
|
"experimentalDecorators": true
|
|
}
|
|
}`,
|
|
},
|
|
entryPaths: []string{"/entry.js"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSExperimentalDecoratorsKeepNames(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
@decoratorMustComeAfterName
|
|
export class Foo {}
|
|
`,
|
|
"/tsconfig.json": `{
|
|
"compilerOptions": {
|
|
"experimentalDecorators": true
|
|
}
|
|
}`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
KeepNames: true,
|
|
},
|
|
})
|
|
}
|
|
|
|
// See: https://github.com/evanw/esbuild/issues/2147
|
|
func TestTSExperimentalDecoratorScopeIssue2147(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
let foo = 1
|
|
class Foo {
|
|
method1(@dec(foo) foo = 2) {}
|
|
method2(@dec(() => foo) foo = 3) {}
|
|
}
|
|
|
|
class Bar {
|
|
static x = class {
|
|
static y = () => {
|
|
let bar = 1
|
|
@dec(bar)
|
|
@dec(() => bar)
|
|
class Baz {
|
|
@dec(bar) method1() {}
|
|
@dec(() => bar) method2() {}
|
|
method3(@dec(() => bar) bar) {}
|
|
method4(@dec(() => bar) bar) {}
|
|
}
|
|
return Baz
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
"/tsconfig.json": `{
|
|
"compilerOptions": {
|
|
"useDefineForClassFields": false,
|
|
"experimentalDecorators": true
|
|
}
|
|
}`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModePassThrough,
|
|
AbsOutputFile: "/out.js",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSExportDefaultTypeIssue316(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
import dc_def, { bar as dc } from './keep/declare-class'
|
|
import dl_def, { bar as dl } from './keep/declare-let'
|
|
import im_def, { bar as im } from './keep/interface-merged'
|
|
import in_def, { bar as _in } from './keep/interface-nested'
|
|
import tn_def, { bar as tn } from './keep/type-nested'
|
|
import vn_def, { bar as vn } from './keep/value-namespace'
|
|
import vnm_def, { bar as vnm } from './keep/value-namespace-merged'
|
|
|
|
import i_def, { bar as i } from './remove/interface'
|
|
import ie_def, { bar as ie } from './remove/interface-exported'
|
|
import t_def, { bar as t } from './remove/type'
|
|
import te_def, { bar as te } from './remove/type-exported'
|
|
import ton_def, { bar as ton } from './remove/type-only-namespace'
|
|
import tone_def, { bar as tone } from './remove/type-only-namespace-exported'
|
|
|
|
export default [
|
|
dc_def, dc,
|
|
dl_def, dl,
|
|
im_def, im,
|
|
in_def, _in,
|
|
tn_def, tn,
|
|
vn_def, vn,
|
|
vnm_def, vnm,
|
|
|
|
i,
|
|
ie,
|
|
t,
|
|
te,
|
|
ton,
|
|
tone,
|
|
]
|
|
`,
|
|
"/keep/declare-class.ts": `
|
|
declare class foo {}
|
|
export default foo
|
|
export let bar = 123
|
|
`,
|
|
"/keep/declare-let.ts": `
|
|
declare let foo: number
|
|
export default foo
|
|
export let bar = 123
|
|
`,
|
|
"/keep/interface-merged.ts": `
|
|
class foo {
|
|
static x = new foo
|
|
}
|
|
interface foo {}
|
|
export default foo
|
|
export let bar = 123
|
|
`,
|
|
"/keep/interface-nested.ts": `
|
|
if (true) {
|
|
interface foo {}
|
|
}
|
|
export default foo
|
|
export let bar = 123
|
|
`,
|
|
"/keep/type-nested.ts": `
|
|
if (true) {
|
|
type foo = number
|
|
}
|
|
export default foo
|
|
export let bar = 123
|
|
`,
|
|
"/keep/value-namespace.ts": `
|
|
namespace foo {
|
|
export let num = 0
|
|
}
|
|
export default foo
|
|
export let bar = 123
|
|
`,
|
|
"/keep/value-namespace-merged.ts": `
|
|
namespace foo {
|
|
export type num = number
|
|
}
|
|
namespace foo {
|
|
export let num = 0
|
|
}
|
|
export default foo
|
|
export let bar = 123
|
|
`,
|
|
"/remove/interface.ts": `
|
|
interface foo { }
|
|
export default foo
|
|
export let bar = 123
|
|
`,
|
|
"/remove/interface-exported.ts": `
|
|
export interface foo { }
|
|
export default foo
|
|
export let bar = 123
|
|
`,
|
|
"/remove/type.ts": `
|
|
type foo = number
|
|
export default foo
|
|
export let bar = 123
|
|
`,
|
|
"/remove/type-exported.ts": `
|
|
export type foo = number
|
|
export default foo
|
|
export let bar = 123
|
|
`,
|
|
"/remove/type-only-namespace.ts": `
|
|
namespace foo {
|
|
export type num = number
|
|
}
|
|
export default foo
|
|
export let bar = 123
|
|
`,
|
|
"/remove/type-only-namespace-exported.ts": `
|
|
export namespace foo {
|
|
export type num = number
|
|
}
|
|
export default foo
|
|
export let bar = 123
|
|
`,
|
|
"/tsconfig.json": `{
|
|
"compilerOptions": {
|
|
"useDefineForClassFields": false
|
|
}
|
|
}`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSImplicitExtensions(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
import './pick-js.js'
|
|
import './pick-ts.js'
|
|
import './pick-jsx.jsx'
|
|
import './pick-tsx.jsx'
|
|
import './order-js.js'
|
|
import './order-jsx.jsx'
|
|
|
|
import 'pkg/foo-js.js'
|
|
import 'pkg/foo-jsx.jsx'
|
|
import 'pkg-exports/xyz-js'
|
|
import 'pkg-exports/xyz-jsx'
|
|
import 'pkg-exports/foo-js.js'
|
|
import 'pkg-exports/foo-jsx.jsx'
|
|
import 'pkg-imports'
|
|
`,
|
|
|
|
"/pick-js.js": `console.log("correct")`,
|
|
"/pick-js.ts": `console.log("wrong")`,
|
|
|
|
"/pick-ts.jsx": `console.log("wrong")`,
|
|
"/pick-ts.ts": `console.log("correct")`,
|
|
|
|
"/pick-jsx.jsx": `console.log("correct")`,
|
|
"/pick-jsx.tsx": `console.log("wrong")`,
|
|
|
|
"/pick-tsx.js": `console.log("wrong")`,
|
|
"/pick-tsx.tsx": `console.log("correct")`,
|
|
|
|
"/order-js.ts": `console.log("correct")`,
|
|
"/order-js.tsx": `console.log("wrong")`,
|
|
|
|
"/order-jsx.ts": `console.log("correct")`,
|
|
"/order-jsx.tsx": `console.log("wrong")`,
|
|
|
|
"/node_modules/pkg/foo-js.ts": `console.log("correct")`,
|
|
"/node_modules/pkg/foo-jsx.tsx": `console.log("correct")`,
|
|
|
|
"/node_modules/pkg-exports/package.json": `{
|
|
"exports": {
|
|
"./xyz-js": "./abc-js.js",
|
|
"./xyz-jsx": "./abc-jsx.jsx",
|
|
"./*": "./lib/*"
|
|
}
|
|
}`,
|
|
"/node_modules/pkg-exports/abc-js.ts": `console.log("correct")`,
|
|
"/node_modules/pkg-exports/abc-jsx.tsx": `console.log("correct")`,
|
|
"/node_modules/pkg-exports/lib/foo-js.ts": `console.log("correct")`,
|
|
"/node_modules/pkg-exports/lib/foo-jsx.tsx": `console.log("correct")`,
|
|
|
|
"/node_modules/pkg-imports/package.json": `{
|
|
"imports": {
|
|
"#xyz-js": "./abc-js.js",
|
|
"#xyz-jsx": "./abc-jsx.jsx",
|
|
"#bar/*": "./lib/*"
|
|
}
|
|
}`,
|
|
"/node_modules/pkg-imports/index.js": `
|
|
import "#xyz-js"
|
|
import "#xyz-jsx"
|
|
import "#bar/foo-js.js"
|
|
import "#bar/foo-jsx.jsx"
|
|
`,
|
|
"/node_modules/pkg-imports/abc-js.ts": `console.log("correct")`,
|
|
"/node_modules/pkg-imports/abc-jsx.tsx": `console.log("correct")`,
|
|
"/node_modules/pkg-imports/lib/foo-js.ts": `console.log("correct")`,
|
|
"/node_modules/pkg-imports/lib/foo-jsx.tsx": `console.log("correct")`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSImplicitExtensionsMissing(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
import './mjs.mjs'
|
|
import './cjs.cjs'
|
|
import './js.js'
|
|
import './jsx.jsx'
|
|
`,
|
|
"/mjs.ts": ``,
|
|
"/mjs.tsx": ``,
|
|
"/cjs.ts": ``,
|
|
"/cjs.tsx": ``,
|
|
"/js.ts.js": ``,
|
|
"/jsx.tsx.jsx": ``,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
},
|
|
expectedScanLog: `entry.ts: ERROR: Could not resolve "./mjs.mjs"
|
|
entry.ts: ERROR: Could not resolve "./cjs.cjs"
|
|
entry.ts: ERROR: Could not resolve "./js.js"
|
|
entry.ts: ERROR: Could not resolve "./jsx.jsx"
|
|
`,
|
|
})
|
|
}
|
|
|
|
func TestExportTypeIssue379(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
import * as A from './a'
|
|
import * as B from './b'
|
|
import * as C from './c'
|
|
import * as D from './d'
|
|
console.log(A, B, C, D)
|
|
`,
|
|
"/a.ts": `
|
|
type Test = Element
|
|
let foo = 123
|
|
export { Test, foo }
|
|
`,
|
|
"/b.ts": `
|
|
export type Test = Element
|
|
export let foo = 123
|
|
`,
|
|
"/c.ts": `
|
|
import { Test } from './test'
|
|
let foo = 123
|
|
export { Test }
|
|
export { foo }
|
|
`,
|
|
"/d.ts": `
|
|
export { Test }
|
|
export { foo }
|
|
import { Test } from './test'
|
|
let foo = 123
|
|
`,
|
|
"/test.ts": `
|
|
export type Test = Element
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
TS: config.TSOptions{Config: config.TSConfig{
|
|
UseDefineForClassFields: config.False,
|
|
}},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestThisInsideFunctionTS(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
function foo(x = this) { console.log(this) }
|
|
const objFoo = {
|
|
foo(x = this) { console.log(this) }
|
|
}
|
|
class Foo {
|
|
x = this
|
|
static y = this.z
|
|
foo(x = this) { console.log(this) }
|
|
static bar(x = this) { console.log(this) }
|
|
}
|
|
new Foo(foo(objFoo))
|
|
if (nested) {
|
|
function bar(x = this) { console.log(this) }
|
|
const objBar = {
|
|
foo(x = this) { console.log(this) }
|
|
}
|
|
class Bar {
|
|
x = this
|
|
static y = this.z
|
|
foo(x = this) { console.log(this) }
|
|
static bar(x = this) { console.log(this) }
|
|
}
|
|
new Bar(bar(objBar))
|
|
}
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
TS: config.TSOptions{Config: config.TSConfig{
|
|
UseDefineForClassFields: config.False,
|
|
}},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestThisInsideFunctionTSUseDefineForClassFields(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
function foo(x = this) { console.log(this) }
|
|
const objFoo = {
|
|
foo(x = this) { console.log(this) }
|
|
}
|
|
class Foo {
|
|
x = this
|
|
static y = this.z
|
|
foo(x = this) { console.log(this) }
|
|
static bar(x = this) { console.log(this) }
|
|
}
|
|
new Foo(foo(objFoo))
|
|
if (nested) {
|
|
function bar(x = this) { console.log(this) }
|
|
const objBar = {
|
|
foo(x = this) { console.log(this) }
|
|
}
|
|
class Bar {
|
|
x = this
|
|
static y = this.z
|
|
foo(x = this) { console.log(this) }
|
|
static bar(x = this) { console.log(this) }
|
|
}
|
|
new Bar(bar(objBar))
|
|
}
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
TS: config.TSOptions{Config: config.TSConfig{
|
|
UseDefineForClassFields: config.True,
|
|
}},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestThisInsideFunctionTSNoBundle(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
function foo(x = this) { console.log(this) }
|
|
const objFoo = {
|
|
foo(x = this) { console.log(this) }
|
|
}
|
|
class Foo {
|
|
x = this
|
|
static y = this.z
|
|
foo(x = this) { console.log(this) }
|
|
static bar(x = this) { console.log(this) }
|
|
}
|
|
new Foo(foo(objFoo))
|
|
if (nested) {
|
|
function bar(x = this) { console.log(this) }
|
|
const objBar = {
|
|
foo(x = this) { console.log(this) }
|
|
}
|
|
class Bar {
|
|
x = this
|
|
static y = this.z
|
|
foo(x = this) { console.log(this) }
|
|
static bar(x = this) { console.log(this) }
|
|
}
|
|
new Bar(bar(objBar))
|
|
}
|
|
`,
|
|
"/tsconfig.json": `{
|
|
"compilerOptions": {
|
|
"useDefineForClassFields": false
|
|
}
|
|
}`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModePassThrough,
|
|
AbsOutputFile: "/out.js",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestThisInsideFunctionTSNoBundleUseDefineForClassFields(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
function foo(x = this) { console.log(this) }
|
|
const objFoo = {
|
|
foo(x = this) { console.log(this) }
|
|
}
|
|
class Foo {
|
|
x = this
|
|
static y = this.z
|
|
foo(x = this) { console.log(this) }
|
|
static bar(x = this) { console.log(this) }
|
|
}
|
|
new Foo(foo(objFoo))
|
|
if (nested) {
|
|
function bar(x = this) { console.log(this) }
|
|
const objBar = {
|
|
foo(x = this) { console.log(this) }
|
|
}
|
|
class Bar {
|
|
x = this
|
|
static y = this.z
|
|
foo(x = this) { console.log(this) }
|
|
static bar(x = this) { console.log(this) }
|
|
}
|
|
new Bar(bar(objBar))
|
|
}
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModePassThrough,
|
|
AbsOutputFile: "/out.js",
|
|
TS: config.TSOptions{Config: config.TSConfig{
|
|
UseDefineForClassFields: config.True,
|
|
}},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSComputedClassFieldUseDefineFalse(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
class Foo {
|
|
[q];
|
|
[r] = s;
|
|
@dec
|
|
[x];
|
|
@dec
|
|
[y] = z;
|
|
}
|
|
new Foo()
|
|
`,
|
|
"/tsconfig.json": `{
|
|
"compilerOptions": {
|
|
"useDefineForClassFields": false,
|
|
"experimentalDecorators": true
|
|
}
|
|
}`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModePassThrough,
|
|
AbsOutputFile: "/out.js",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSComputedClassFieldUseDefineTrue(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
class Foo {
|
|
[q];
|
|
[r] = s;
|
|
@dec
|
|
[x];
|
|
@dec
|
|
[y] = z;
|
|
}
|
|
new Foo()
|
|
`,
|
|
"/tsconfig.json": `{
|
|
"compilerOptions": {
|
|
"useDefineForClassFields": true,
|
|
"experimentalDecorators": true
|
|
}
|
|
}`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModePassThrough,
|
|
AbsOutputFile: "/out.js",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSComputedClassFieldUseDefineTrueLower(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
class Foo {
|
|
[q];
|
|
[r] = s;
|
|
@dec
|
|
[x];
|
|
@dec
|
|
[y] = z;
|
|
}
|
|
new Foo()
|
|
`,
|
|
"/tsconfig.json": `{
|
|
"compilerOptions": {
|
|
"useDefineForClassFields": true,
|
|
"experimentalDecorators": true
|
|
}
|
|
}`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModePassThrough,
|
|
AbsOutputFile: "/out.js",
|
|
UnsupportedJSFeatures: compat.ClassField,
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSAbstractClassFieldUseAssign(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
const keepThis = Symbol('keepThis')
|
|
declare const AND_REMOVE_THIS: unique symbol
|
|
abstract class Foo {
|
|
REMOVE_THIS: any
|
|
[keepThis]: any
|
|
abstract REMOVE_THIS_TOO: any
|
|
abstract [AND_REMOVE_THIS]: any
|
|
abstract [(x => y => x + y)('nested')('scopes')]: any
|
|
}
|
|
(() => new Foo())()
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModePassThrough,
|
|
AbsOutputFile: "/out.js",
|
|
TS: config.TSOptions{Config: config.TSConfig{
|
|
UseDefineForClassFields: config.False,
|
|
}},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSAbstractClassFieldUseDefine(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
const keepThisToo = Symbol('keepThisToo')
|
|
declare const REMOVE_THIS_TOO: unique symbol
|
|
abstract class Foo {
|
|
keepThis: any
|
|
[keepThisToo]: any
|
|
abstract REMOVE_THIS: any
|
|
abstract [REMOVE_THIS_TOO]: any
|
|
abstract [(x => y => x + y)('nested')('scopes')]: any
|
|
}
|
|
(() => new Foo())()
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModePassThrough,
|
|
AbsOutputFile: "/out.js",
|
|
TS: config.TSOptions{Config: config.TSConfig{
|
|
UseDefineForClassFields: config.True,
|
|
}},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSImportMTS(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
import './imported.mjs'
|
|
`,
|
|
"/imported.mts": `
|
|
console.log('works')
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
OutputFormat: config.FormatESModule,
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSImportCTS(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
require('./required.cjs')
|
|
`,
|
|
"/required.cjs": `
|
|
console.log('works')
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
OutputFormat: config.FormatCommonJS,
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSSideEffectsFalseWarningTypeDeclarations(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
import "some-js"
|
|
import "some-ts"
|
|
import "empty-js"
|
|
import "empty-ts"
|
|
import "empty-dts"
|
|
`,
|
|
|
|
"/node_modules/some-js/package.json": `{ "main": "./foo.js", "sideEffects": false }`,
|
|
"/node_modules/some-js/foo.js": `console.log('foo')`,
|
|
|
|
"/node_modules/some-ts/package.json": `{ "main": "./foo.ts", "sideEffects": false }`,
|
|
"/node_modules/some-ts/foo.ts": `console.log('foo' as string)`,
|
|
|
|
"/node_modules/empty-js/package.json": `{ "main": "./foo.js", "sideEffects": false }`,
|
|
"/node_modules/empty-js/foo.js": ``,
|
|
|
|
"/node_modules/empty-ts/package.json": `{ "main": "./foo.ts", "sideEffects": false }`,
|
|
"/node_modules/empty-ts/foo.ts": `export type Foo = number`,
|
|
|
|
"/node_modules/empty-dts/package.json": `{ "main": "./foo.d.ts", "sideEffects": false }`,
|
|
"/node_modules/empty-dts/foo.d.ts": `export type Foo = number`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
},
|
|
expectedScanLog: `entry.ts: WARNING: Ignoring this import because "node_modules/some-js/foo.js" was marked as having no side effects
|
|
node_modules/some-js/package.json: NOTE: "sideEffects" is false in the enclosing "package.json" file:
|
|
entry.ts: WARNING: Ignoring this import because "node_modules/some-ts/foo.ts" was marked as having no side effects
|
|
node_modules/some-ts/package.json: NOTE: "sideEffects" is false in the enclosing "package.json" file:
|
|
`,
|
|
})
|
|
}
|
|
|
|
func TestTSSiblingNamespace(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/let.ts": `
|
|
export namespace x { export let y = 123 }
|
|
export namespace x { export let z = y }
|
|
`,
|
|
"/function.ts": `
|
|
export namespace x { export function y() {} }
|
|
export namespace x { export let z = y }
|
|
`,
|
|
"/class.ts": `
|
|
export namespace x { export class y {} }
|
|
export namespace x { export let z = y }
|
|
`,
|
|
"/namespace.ts": `
|
|
export namespace x { export namespace y { 0 } }
|
|
export namespace x { export let z = y }
|
|
`,
|
|
"/enum.ts": `
|
|
export namespace x { export enum y {} }
|
|
export namespace x { export let z = y }
|
|
`,
|
|
},
|
|
entryPaths: []string{
|
|
"/let.ts",
|
|
"/function.ts",
|
|
"/class.ts",
|
|
"/namespace.ts",
|
|
"/enum.ts",
|
|
},
|
|
options: config.Options{
|
|
Mode: config.ModePassThrough,
|
|
AbsOutputDir: "/out",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSSiblingEnum(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/number.ts": `
|
|
export enum x { y, yy = y }
|
|
export enum x { z = y + 1 }
|
|
|
|
declare let y: any, z: any
|
|
export namespace x { console.log(y, z) }
|
|
console.log(x.y, x.z)
|
|
`,
|
|
"/string.ts": `
|
|
export enum x { y = 'a', yy = y }
|
|
export enum x { z = y }
|
|
|
|
declare let y: any, z: any
|
|
export namespace x { console.log(y, z) }
|
|
console.log(x.y, x.z)
|
|
`,
|
|
"/propagation.ts": `
|
|
export enum a { b = 100 }
|
|
export enum x {
|
|
c = a.b,
|
|
d = c * 2,
|
|
e = x.d ** 2,
|
|
f = x['e'] / 4,
|
|
}
|
|
export enum x { g = f >> 4 }
|
|
console.log(a.b, a['b'], x.g, x['g'])
|
|
`,
|
|
"/nested-number.ts": `
|
|
export namespace foo { export enum x { y, yy = y } }
|
|
export namespace foo { export enum x { z = y + 1 } }
|
|
|
|
declare let y: any, z: any
|
|
export namespace foo.x {
|
|
console.log(y, z)
|
|
console.log(x.y, x.z)
|
|
}
|
|
`,
|
|
"/nested-string.ts": `
|
|
export namespace foo { export enum x { y = 'a', yy = y } }
|
|
export namespace foo { export enum x { z = y } }
|
|
|
|
declare let y: any, z: any
|
|
export namespace foo.x {
|
|
console.log(y, z)
|
|
console.log(x.y, x.z)
|
|
}
|
|
`,
|
|
"/nested-propagation.ts": `
|
|
export namespace n { export enum a { b = 100 } }
|
|
export namespace n {
|
|
export enum x {
|
|
c = n.a.b,
|
|
d = c * 2,
|
|
e = x.d ** 2,
|
|
f = x['e'] / 4,
|
|
}
|
|
}
|
|
export namespace n {
|
|
export enum x { g = f >> 4 }
|
|
console.log(a.b, n.a.b, n['a']['b'], x.g, n.x.g, n['x']['g'])
|
|
}
|
|
`,
|
|
},
|
|
entryPaths: []string{
|
|
"/number.ts",
|
|
"/string.ts",
|
|
"/propagation.ts",
|
|
"/nested-number.ts",
|
|
"/nested-string.ts",
|
|
"/nested-propagation.ts",
|
|
},
|
|
options: config.Options{
|
|
Mode: config.ModePassThrough,
|
|
AbsOutputDir: "/out",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSEnumTreeShaking(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/simple-member.ts": `
|
|
enum x { y = 123 }
|
|
console.log(x.y)
|
|
`,
|
|
"/simple-enum.ts": `
|
|
enum x { y = 123 }
|
|
console.log(x)
|
|
`,
|
|
"/sibling-member.ts": `
|
|
enum x { y = 123 }
|
|
enum x { z = y * 2 }
|
|
console.log(x.y, x.z)
|
|
`,
|
|
"/sibling-enum-before.ts": `
|
|
console.log(x)
|
|
enum x { y = 123 }
|
|
enum x { z = y * 2 }
|
|
`,
|
|
"/sibling-enum-middle.ts": `
|
|
enum x { y = 123 }
|
|
console.log(x)
|
|
enum x { z = y * 2 }
|
|
`,
|
|
"/sibling-enum-after.ts": `
|
|
enum x { y = 123 }
|
|
enum x { z = y * 2 }
|
|
console.log(x)
|
|
`,
|
|
"/namespace-before.ts": `
|
|
namespace x { console.log(x, y) }
|
|
enum x { y = 123 }
|
|
`,
|
|
"/namespace-after.ts": `
|
|
enum x { y = 123 }
|
|
namespace x { console.log(x, y) }
|
|
`,
|
|
},
|
|
entryPaths: []string{
|
|
"/simple-member.ts",
|
|
"/simple-enum.ts",
|
|
"/sibling-member.ts",
|
|
"/sibling-enum-before.ts",
|
|
"/sibling-enum-middle.ts",
|
|
"/sibling-enum-after.ts",
|
|
"/namespace-before.ts",
|
|
"/namespace-after.ts",
|
|
},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputDir: "/out",
|
|
OutputFormat: config.FormatESModule,
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSEnumJSX(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/element.tsx": `
|
|
export enum Foo { Div = 'div' }
|
|
console.log(<Foo.Div />)
|
|
`,
|
|
"/fragment.tsx": `
|
|
export enum React { Fragment = 'div' }
|
|
console.log(<>test</>)
|
|
`,
|
|
"/nested-element.tsx": `
|
|
namespace x.y { export enum Foo { Div = 'div' } }
|
|
namespace x.y { console.log(<x.y.Foo.Div />) }
|
|
`,
|
|
"/nested-fragment.tsx": `
|
|
namespace x.y { export enum React { Fragment = 'div' } }
|
|
namespace x.y { console.log(<>test</>) }
|
|
`,
|
|
},
|
|
entryPaths: []string{
|
|
"/element.tsx",
|
|
"/fragment.tsx",
|
|
"/nested-element.tsx",
|
|
"/nested-fragment.tsx",
|
|
},
|
|
options: config.Options{
|
|
Mode: config.ModePassThrough,
|
|
AbsOutputDir: "/out",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSEnumDefine(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
enum a { b = 123, c = d }
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModePassThrough,
|
|
AbsOutputDir: "/out",
|
|
Defines: &config.ProcessedDefines{
|
|
IdentifierDefines: map[string]config.DefineData{
|
|
"d": {
|
|
DefineExpr: &config.DefineExpr{
|
|
Parts: []string{"b"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSEnumSameModuleInliningAccess(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
enum a_num { x = 123 }
|
|
enum b_num { x = 123 }
|
|
enum c_num { x = 123 }
|
|
enum d_num { x = 123 }
|
|
enum e_num { x = 123 }
|
|
|
|
enum a_str { x = 'abc' }
|
|
enum b_str { x = 'abc' }
|
|
enum c_str { x = 'abc' }
|
|
enum d_str { x = 'abc' }
|
|
enum e_str { x = 'abc' }
|
|
|
|
inlined = [
|
|
a_num.x,
|
|
b_num['x'],
|
|
|
|
a_str.x,
|
|
b_str['x'],
|
|
]
|
|
|
|
not_inlined = [
|
|
c_num?.x,
|
|
d_num?.['x'],
|
|
e_num,
|
|
|
|
c_str?.x,
|
|
d_str?.['x'],
|
|
e_str,
|
|
]
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputDir: "/out",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSEnumCrossModuleInliningAccess(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
import {
|
|
a_num, b_num, c_num, d_num, e_num,
|
|
a_str, b_str, c_str, d_str, e_str,
|
|
} from './enums'
|
|
|
|
inlined = [
|
|
a_num.x,
|
|
b_num['x'],
|
|
|
|
a_str.x,
|
|
b_str['x'],
|
|
]
|
|
|
|
not_inlined = [
|
|
c_num?.x,
|
|
d_num?.['x'],
|
|
e_num,
|
|
|
|
c_str?.x,
|
|
d_str?.['x'],
|
|
e_str,
|
|
]
|
|
`,
|
|
"/enums.ts": `
|
|
export enum a_num { x = 123 }
|
|
export enum b_num { x = 123 }
|
|
export enum c_num { x = 123 }
|
|
export enum d_num { x = 123 }
|
|
export enum e_num { x = 123 }
|
|
|
|
export enum a_str { x = 'abc' }
|
|
export enum b_str { x = 'abc' }
|
|
export enum c_str { x = 'abc' }
|
|
export enum d_str { x = 'abc' }
|
|
export enum e_str { x = 'abc' }
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputDir: "/out",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSEnumCrossModuleInliningDefinitions(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
import { a } from './enums'
|
|
console.log([
|
|
a.implicit_number,
|
|
a.explicit_number,
|
|
a.explicit_string,
|
|
a.non_constant,
|
|
])
|
|
`,
|
|
"/enums.ts": `
|
|
export enum a {
|
|
implicit_number,
|
|
explicit_number = 123,
|
|
explicit_string = 'xyz',
|
|
non_constant = foo,
|
|
}
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputDir: "/out",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSEnumCrossModuleInliningReExport(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.js": `
|
|
import { a } from './re-export'
|
|
import { b } from './re-export-star'
|
|
import * as ns from './enums'
|
|
console.log([
|
|
a.x,
|
|
b.x,
|
|
ns.c.x,
|
|
])
|
|
`,
|
|
"/re-export.js": `
|
|
export { a } from './enums'
|
|
`,
|
|
"/re-export-star.js": `
|
|
export * from './enums'
|
|
`,
|
|
"/enums.ts": `
|
|
export enum a { x = 'a' }
|
|
export enum b { x = 'b' }
|
|
export enum c { x = 'c' }
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.js"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputDir: "/out",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSEnumCrossModuleInliningMinifyIndexIntoDot(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
const enum Foo {
|
|
foo1 = 'abc',
|
|
foo2 = 'a b c',
|
|
}
|
|
import { Bar } from './lib'
|
|
inlined = [
|
|
obj[Foo.foo1],
|
|
obj[Bar.bar1],
|
|
obj?.[Foo.foo1],
|
|
obj?.[Bar.bar1],
|
|
obj?.prop[Foo.foo1],
|
|
obj?.prop[Bar.bar1],
|
|
]
|
|
notInlined = [
|
|
obj[Foo.foo2],
|
|
obj[Bar.bar2],
|
|
obj?.[Foo.foo2],
|
|
obj?.[Bar.bar2],
|
|
obj?.prop[Foo.foo2],
|
|
obj?.prop[Bar.bar2],
|
|
]
|
|
`,
|
|
"/lib.ts": `
|
|
export const enum Bar {
|
|
bar1 = 'xyz',
|
|
bar2 = 'x y z',
|
|
}
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
MinifySyntax: true,
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSEnumCrossModuleTreeShaking(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
import {
|
|
a_DROP,
|
|
b_DROP,
|
|
c_DROP,
|
|
} from './enums'
|
|
|
|
console.log([
|
|
a_DROP.x,
|
|
b_DROP['x'],
|
|
c_DROP.x,
|
|
])
|
|
|
|
import {
|
|
a_keep,
|
|
b_keep,
|
|
c_keep,
|
|
d_keep,
|
|
e_keep,
|
|
} from './enums'
|
|
|
|
console.log([
|
|
a_keep.x,
|
|
b_keep.x,
|
|
c_keep,
|
|
d_keep.y,
|
|
e_keep.x,
|
|
])
|
|
`,
|
|
"/enums.ts": `
|
|
export enum a_DROP { x = 1 } // test a dot access
|
|
export enum b_DROP { x = 2 } // test an index access
|
|
export enum c_DROP { x = '' } // test a string enum
|
|
|
|
export enum a_keep { x = false } // false is not inlinable
|
|
export enum b_keep { x = foo } // foo has side effects
|
|
export enum c_keep { x = 3 } // this enum object is captured
|
|
export enum d_keep { x = 4 } // we access "y" on this object
|
|
export let e_keep = {} // non-enum properties should be kept
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputDir: "/out",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSEnumExportClause(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
import {
|
|
A,
|
|
B,
|
|
C as c,
|
|
d as dd,
|
|
} from './enums'
|
|
|
|
console.log([
|
|
A.A,
|
|
B.B,
|
|
c.C,
|
|
dd.D,
|
|
])
|
|
`,
|
|
"/enums.ts": `
|
|
export enum A { A = 1 }
|
|
enum B { B = 2 }
|
|
export enum C { C = 3 }
|
|
enum D { D = 4 }
|
|
export { B, D as d }
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputDir: "/out",
|
|
},
|
|
})
|
|
}
|
|
|
|
// This checks that we don't generate a warning for code that the TypeScript
|
|
// compiler generates that looks like this:
|
|
//
|
|
// var __rest = (this && this.__rest) || function (s, e) {
|
|
// ...
|
|
// };
|
|
func TestTSThisIsUndefinedWarning(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/warning1.ts": `export var foo = this`,
|
|
"/warning2.ts": `export var foo = this || this.foo`,
|
|
"/warning3.ts": `export var foo = this ? this.foo : null`,
|
|
|
|
"/silent1.ts": `export var foo = this && this.foo`,
|
|
"/silent2.ts": `export var foo = this && (() => this.foo)`,
|
|
},
|
|
entryPaths: []string{
|
|
"/warning1.ts",
|
|
"/warning2.ts",
|
|
"/warning3.ts",
|
|
|
|
"/silent1.ts",
|
|
"/silent2.ts",
|
|
},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputDir: "/out",
|
|
},
|
|
debugLogs: true,
|
|
expectedScanLog: `warning1.ts: DEBUG: Top-level "this" will be replaced with undefined since this file is an ECMAScript module
|
|
warning1.ts: NOTE: This file is considered to be an ECMAScript module because of the "export" keyword here:
|
|
warning2.ts: DEBUG: Top-level "this" will be replaced with undefined since this file is an ECMAScript module
|
|
warning2.ts: NOTE: This file is considered to be an ECMAScript module because of the "export" keyword here:
|
|
warning3.ts: DEBUG: Top-level "this" will be replaced with undefined since this file is an ECMAScript module
|
|
warning3.ts: NOTE: This file is considered to be an ECMAScript module because of the "export" keyword here:
|
|
`,
|
|
})
|
|
}
|
|
|
|
func TestTSCommonJSVariableInESMTypeModule(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `module.exports = null`,
|
|
"/package.json": `{ "type": "module" }`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
},
|
|
expectedScanLog: `entry.ts: WARNING: The CommonJS "module" variable is treated as a global variable in an ECMAScript module and may not work as expected
|
|
package.json: NOTE: This file is considered to be an ECMAScript module because the enclosing "package.json" file sets the type of this file to "module":
|
|
NOTE: Node's package format requires that CommonJS files in a "type": "module" package use the ".cjs" file extension. If you are using TypeScript, you can use the ".cts" file extension with esbuild instead.
|
|
`,
|
|
})
|
|
}
|
|
|
|
func TestEnumRulesFrom_TypeScript_5_0(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/supported.ts": `
|
|
// From https://github.com/microsoft/TypeScript/pull/50528:
|
|
// "An expression is considered a constant expression if it is
|
|
const enum Foo {
|
|
// a number or string literal,
|
|
X0 = 123,
|
|
X1 = 'x',
|
|
|
|
// a unary +, -, or ~ applied to a numeric constant expression,
|
|
X2 = +1,
|
|
X3 = -2,
|
|
X4 = ~3,
|
|
|
|
// a binary +, -, *, /, %, **, <<, >>, >>>, |, &, ^ applied to two numeric constant expressions,
|
|
X5 = 1 + 2,
|
|
X6 = 1 - 2,
|
|
X7 = 2 * 3,
|
|
X8 = 1 / 2,
|
|
X9 = 3 % 2,
|
|
X10 = 2 ** 3,
|
|
X11 = 1 << 2,
|
|
X12 = -9 >> 1,
|
|
X13 = -9 >>> 1,
|
|
X14 = 5 | 12,
|
|
X15 = 5 & 12,
|
|
X16 = 5 ^ 12,
|
|
|
|
// a binary + applied to two constant expressions whereof at least one is a string,
|
|
X17 = 'x' + 0,
|
|
X18 = 0 + 'x',
|
|
X19 = 'x' + 'y',
|
|
X20 = '' + NaN,
|
|
X21 = '' + Infinity,
|
|
X22 = '' + -Infinity,
|
|
X23 = '' + -0,
|
|
|
|
// a template expression where each substitution expression is a constant expression,
|
|
X24 = ` + "`A${0}B${'x'}C${1 + 3 - 4 / 2 * 5 ** 6}D`" + `,
|
|
|
|
// a parenthesized constant expression,
|
|
X25 = (321),
|
|
|
|
// a dotted name (e.g. x.y.z) that references a const variable with a constant expression initializer and no type annotation,
|
|
/* (we don't implement this one) */
|
|
|
|
// a dotted name that references an enum member with an enum literal type, or
|
|
X26 = X0,
|
|
X27 = X0 + 'x',
|
|
X28 = 'x' + X0,
|
|
X29 = ` + "`a${X0}b`" + `,
|
|
X30 = Foo.X0,
|
|
X31 = Foo.X0 + 'x',
|
|
X32 = 'x' + Foo.X0,
|
|
X33 = ` + "`a${Foo.X0}b`" + `,
|
|
|
|
// a dotted name indexed by a string literal (e.g. x.y["z"]) that references an enum member with an enum literal type."
|
|
X34 = X1,
|
|
X35 = X1 + 'y',
|
|
X36 = 'y' + X1,
|
|
X37 = ` + "`a${X1}b`" + `,
|
|
X38 = Foo['X1'],
|
|
X39 = Foo['X1'] + 'y',
|
|
X40 = 'y' + Foo['X1'],
|
|
X41 = ` + "`a${Foo['X1']}b`" + `,
|
|
}
|
|
|
|
console.log(
|
|
// a number or string literal,
|
|
Foo.X0,
|
|
Foo.X1,
|
|
|
|
// a unary +, -, or ~ applied to a numeric constant expression,
|
|
Foo.X2,
|
|
Foo.X3,
|
|
Foo.X4,
|
|
|
|
// a binary +, -, *, /, %, **, <<, >>, >>>, |, &, ^ applied to two numeric constant expressions,
|
|
Foo.X5,
|
|
Foo.X6,
|
|
Foo.X7,
|
|
Foo.X8,
|
|
Foo.X9,
|
|
Foo.X10,
|
|
Foo.X11,
|
|
Foo.X12,
|
|
Foo.X13,
|
|
Foo.X14,
|
|
Foo.X15,
|
|
Foo.X16,
|
|
|
|
// a template expression where each substitution expression is a constant expression,
|
|
Foo.X17,
|
|
Foo.X18,
|
|
Foo.X19,
|
|
Foo.X20,
|
|
Foo.X21,
|
|
Foo.X22,
|
|
Foo.X23,
|
|
|
|
// a template expression where each substitution expression is a constant expression,
|
|
Foo.X24,
|
|
|
|
// a parenthesized constant expression,
|
|
Foo.X25,
|
|
|
|
// a dotted name that references an enum member with an enum literal type, or
|
|
Foo.X26,
|
|
Foo.X27,
|
|
Foo.X28,
|
|
Foo.X29,
|
|
Foo.X30,
|
|
Foo.X31,
|
|
Foo.X32,
|
|
Foo.X33,
|
|
|
|
// a dotted name indexed by a string literal (e.g. x.y["z"]) that references an enum member with an enum literal type."
|
|
Foo.X34,
|
|
Foo.X35,
|
|
Foo.X36,
|
|
Foo.X37,
|
|
Foo.X38,
|
|
Foo.X39,
|
|
Foo.X40,
|
|
Foo.X41,
|
|
)
|
|
`,
|
|
"/not-supported.ts": `
|
|
const enum NonIntegerNumberToString {
|
|
SUPPORTED = '' + 1,
|
|
UNSUPPORTED = '' + 1.5,
|
|
}
|
|
console.log(
|
|
NonIntegerNumberToString.SUPPORTED,
|
|
NonIntegerNumberToString.UNSUPPORTED,
|
|
)
|
|
|
|
const enum OutOfBoundsNumberToString {
|
|
SUPPORTED = '' + 1_000_000_000,
|
|
UNSUPPORTED = '' + 1_000_000_000_000,
|
|
}
|
|
console.log(
|
|
OutOfBoundsNumberToString.SUPPORTED,
|
|
OutOfBoundsNumberToString.UNSUPPORTED,
|
|
)
|
|
|
|
const enum TemplateExpressions {
|
|
// TypeScript enums don't handle any of these
|
|
NULL = '' + null,
|
|
TRUE = '' + true,
|
|
FALSE = '' + false,
|
|
BIGINT = '' + 123n,
|
|
}
|
|
console.log(
|
|
TemplateExpressions.NULL,
|
|
TemplateExpressions.TRUE,
|
|
TemplateExpressions.FALSE,
|
|
TemplateExpressions.BIGINT,
|
|
)
|
|
`,
|
|
},
|
|
entryPaths: []string{
|
|
"/supported.ts",
|
|
"/not-supported.ts",
|
|
},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputDir: "/out",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSEnumUseBeforeDeclare(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
export function before() {
|
|
console.log(Foo.FOO)
|
|
}
|
|
enum Foo { FOO }
|
|
export function after() {
|
|
console.log(Foo.FOO)
|
|
}
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputDir: "/out",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSPreferJSOverTSInsideNodeModules(t *testing.T) {
|
|
// We now prefer ".js" over ".ts" inside "node_modules"
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/Users/user/project/src/main.ts": `
|
|
// Implicit extensions
|
|
import './relative/path'
|
|
import 'package/path'
|
|
|
|
// Explicit extensions
|
|
import './relative2/path.js'
|
|
import 'package2/path.js'
|
|
`,
|
|
|
|
"/Users/user/project/src/relative/path.ts": `console.log('success')`,
|
|
"/Users/user/project/src/relative/path.js": `console.log('FAILURE')`,
|
|
|
|
"/Users/user/project/src/relative2/path.ts": `console.log('FAILURE')`,
|
|
"/Users/user/project/src/relative2/path.js": `console.log('success')`,
|
|
|
|
"/Users/user/project/node_modules/package/path.ts": `console.log('FAILURE')`,
|
|
"/Users/user/project/node_modules/package/path.js": `console.log('success')`,
|
|
|
|
"/Users/user/project/node_modules/package2/path.ts": `console.log('FAILURE')`,
|
|
"/Users/user/project/node_modules/package2/path.js": `console.log('success')`,
|
|
},
|
|
entryPaths: []string{"/Users/user/project/src/main.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputDir: "/out",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSExperimentalDecoratorsManglePropsDefineSemantics(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
class Foo {
|
|
@dec(1) prop1 = null
|
|
@dec(2) prop2_ = null
|
|
@dec(3) ['prop3'] = null
|
|
@dec(4) ['prop4_'] = null
|
|
@dec(5) [/* @__KEY__ */ 'prop5'] = null
|
|
@dec(6) [/* @__KEY__ */ 'prop6_'] = null
|
|
}
|
|
`,
|
|
"/tsconfig.json": `{
|
|
"compilerOptions": {
|
|
"experimentalDecorators": true,
|
|
"useDefineForClassFields": true,
|
|
},
|
|
}`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
MangleProps: regexp.MustCompile("_$"),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSExperimentalDecoratorsManglePropsAssignSemantics(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
class Foo {
|
|
@dec(1) prop1 = null
|
|
@dec(2) prop2_ = null
|
|
@dec(3) ['prop3'] = null
|
|
@dec(4) ['prop4_'] = null
|
|
@dec(5) [/* @__KEY__ */ 'prop5'] = null
|
|
@dec(6) [/* @__KEY__ */ 'prop6_'] = null
|
|
}
|
|
`,
|
|
"/tsconfig.json": `{
|
|
"compilerOptions": {
|
|
"experimentalDecorators": true,
|
|
"useDefineForClassFields": false,
|
|
},
|
|
}`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
MangleProps: regexp.MustCompile("_$"),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSExperimentalDecoratorsManglePropsMethods(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
class Foo {
|
|
@dec(1) prop1() {}
|
|
@dec(2) prop2_() {}
|
|
@dec(3) ['prop3']() {}
|
|
@dec(4) ['prop4_']() {}
|
|
@dec(5) [/* @__KEY__ */ 'prop5']() {}
|
|
@dec(6) [/* @__KEY__ */ 'prop6_']() {}
|
|
}
|
|
`,
|
|
"/tsconfig.json": `{
|
|
"compilerOptions": {
|
|
"experimentalDecorators": true,
|
|
},
|
|
}`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
MangleProps: regexp.MustCompile("_$"),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSExperimentalDecoratorsManglePropsStaticDefineSemantics(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
class Foo {
|
|
@dec(1) static prop1 = null
|
|
@dec(2) static prop2_ = null
|
|
@dec(3) static ['prop3'] = null
|
|
@dec(4) static ['prop4_'] = null
|
|
@dec(5) static [/* @__KEY__ */ 'prop5'] = null
|
|
@dec(6) static [/* @__KEY__ */ 'prop6_'] = null
|
|
}
|
|
`,
|
|
"/tsconfig.json": `{
|
|
"compilerOptions": {
|
|
"experimentalDecorators": true,
|
|
"useDefineForClassFields": true,
|
|
},
|
|
}`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
MangleProps: regexp.MustCompile("_$"),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSExperimentalDecoratorsManglePropsStaticAssignSemantics(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
class Foo {
|
|
@dec(1) static prop1 = null
|
|
@dec(2) static prop2_ = null
|
|
@dec(3) static ['prop3'] = null
|
|
@dec(4) static ['prop4_'] = null
|
|
@dec(5) static [/* @__KEY__ */ 'prop5'] = null
|
|
@dec(6) static [/* @__KEY__ */ 'prop6_'] = null
|
|
}
|
|
`,
|
|
"/tsconfig.json": `{
|
|
"compilerOptions": {
|
|
"experimentalDecorators": true,
|
|
"useDefineForClassFields": false,
|
|
},
|
|
}`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
MangleProps: regexp.MustCompile("_$"),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSExperimentalDecoratorsManglePropsStaticMethods(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
class Foo {
|
|
@dec(1) static prop1() {}
|
|
@dec(2) static prop2_() {}
|
|
@dec(3) static ['prop3']() {}
|
|
@dec(4) static ['prop4_']() {}
|
|
@dec(5) static [/* @__KEY__ */ 'prop5']() {}
|
|
@dec(6) static [/* @__KEY__ */ 'prop6_']() {}
|
|
}
|
|
`,
|
|
"/tsconfig.json": `{
|
|
"compilerOptions": {
|
|
"experimentalDecorators": true,
|
|
},
|
|
}`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
MangleProps: regexp.MustCompile("_$"),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSPrintNonFiniteNumberInsideWith(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
// Use const enums to force inline values
|
|
const enum Foo {
|
|
NAN = 0 / 0,
|
|
POS_INF = 1 / 0,
|
|
NEG_INF = -1 / 0,
|
|
}
|
|
|
|
//! It's ok to use "NaN" and "Infinity" here
|
|
console.log(
|
|
Foo.NAN,
|
|
Foo.POS_INF,
|
|
Foo.NEG_INF,
|
|
)
|
|
checkPrecedence(
|
|
1 / Foo.NAN,
|
|
1 / Foo.POS_INF,
|
|
1 / Foo.NEG_INF,
|
|
)
|
|
|
|
//! We must not use "NaN" or "Infinity" inside "with"
|
|
with (x) {
|
|
console.log(
|
|
Foo.NAN,
|
|
Foo.POS_INF,
|
|
Foo.NEG_INF,
|
|
)
|
|
checkPrecedence(
|
|
1 / Foo.NAN,
|
|
1 / Foo.POS_INF,
|
|
1 / Foo.NEG_INF,
|
|
)
|
|
}
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModePassThrough,
|
|
AbsOutputFile: "/out.js",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestTSImportInNodeModulesNameCollisionWithCSS(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
import "pkg"
|
|
`,
|
|
"/node_modules/pkg/index.ts": `
|
|
import js_ts_css from "./js_ts_css"
|
|
import ts_css from "./ts_css"
|
|
import js_ts from "./js_ts"
|
|
js_ts_css()
|
|
ts_css()
|
|
js_ts()
|
|
`,
|
|
"/node_modules/pkg/js_ts_css.js": `
|
|
import './js_ts_css.css'
|
|
export default function() {}
|
|
`,
|
|
"/node_modules/pkg/js_ts_css.ts": `
|
|
TEST FAILED
|
|
`,
|
|
"/node_modules/pkg/js_ts_css.css": `
|
|
.js_ts_css {}
|
|
`,
|
|
"/node_modules/pkg/ts_css.ts": `
|
|
import './ts_css.css'
|
|
export default function() {}
|
|
`,
|
|
"/node_modules/pkg/ts_css.css": `
|
|
.ts_css {}
|
|
`,
|
|
"/node_modules/pkg/js_ts.js": `
|
|
export default function() {}
|
|
`,
|
|
"/node_modules/pkg/js_ts.ts": `
|
|
TEST FAILED
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
},
|
|
})
|
|
}
|
|
|
|
// See: https://github.com/evanw/esbuild/issues/4421
|
|
func TestParameterPropsUseDefineForClassFieldsTrue(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
class Foo {
|
|
static { console.log('a') }
|
|
a = 1
|
|
static { console.log('b') }
|
|
constructor(public b1 = 2.1, public b2 = 2.2) {
|
|
}
|
|
static { console.log('c') }
|
|
c = 3
|
|
}
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
TS: config.TSOptions{Config: config.TSConfig{
|
|
UseDefineForClassFields: config.True,
|
|
}},
|
|
},
|
|
})
|
|
}
|
|
|
|
// See: https://github.com/evanw/esbuild/issues/4421
|
|
func TestParameterPropsUseDefineForClassFieldsFalse(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
class Foo {
|
|
static { console.log('a') }
|
|
a = 1
|
|
static { console.log('b') }
|
|
constructor(public b1 = 2.1, public b2 = 2.2) {
|
|
}
|
|
static { console.log('c') }
|
|
c = 3
|
|
}
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
TS: config.TSOptions{Config: config.TSConfig{
|
|
UseDefineForClassFields: config.False,
|
|
}},
|
|
},
|
|
})
|
|
}
|
|
|
|
// See: https://github.com/evanw/esbuild/issues/4421
|
|
func TestParameterPropsUseDefineForClassFieldsTrueLowered(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
class Foo {
|
|
static { console.log('a') }
|
|
a = 1
|
|
static { console.log('b') }
|
|
constructor(public b1 = 2.1, public b2 = 2.2) {
|
|
}
|
|
static { console.log('c') }
|
|
c = 3
|
|
}
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
TS: config.TSOptions{Config: config.TSConfig{
|
|
UseDefineForClassFields: config.True,
|
|
}},
|
|
UnsupportedJSFeatures: compat.ClassField,
|
|
},
|
|
})
|
|
}
|
|
|
|
// See: https://github.com/evanw/esbuild/issues/4507
|
|
func TestTreeShakingImportAliasIssue4507(t *testing.T) {
|
|
ts_suite.expectBundled(t, bundled{
|
|
files: map[string]string{
|
|
"/entry.ts": `
|
|
import Base from './dep.js';
|
|
import Alias = Base.SomeType;
|
|
|
|
export function make() {
|
|
return new Base();
|
|
}
|
|
`,
|
|
"/dep.js": `
|
|
export default class Base {
|
|
static hello() { return 'dep code is present'; }
|
|
}
|
|
`,
|
|
},
|
|
entryPaths: []string{"/entry.ts"},
|
|
options: config.Options{
|
|
Mode: config.ModeBundle,
|
|
AbsOutputFile: "/out.js",
|
|
},
|
|
})
|
|
}
|