diff --git a/CHANGELOG.md b/CHANGELOG.md index 781d763ab64..c16bc0b0160 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,26 @@ ## Unreleased +* Fix compilation of abstract class fields in TypeScript ([#1623](https://github.com/evanw/esbuild/issues/1623)) + + This release fixes a bug where esbuild could incorrectly include a TypeScript abstract class field in the compiled JavaScript output. This is incorrect because the official TypeScript compiler never does this. Note that this only happened in scenarios where TypeScript's `useDefineForClassFields` setting was set to `true` (or equivalently where TypeScript's `target` setting was set to `ESNext`). Here is the difference: + + ```js + // Original code + abstract class Foo { + abstract foo: any; + } + + // Old output + class Foo { + foo; + } + + // New output + class Foo { + } + ``` + * Proxy from the `__require` shim to `require` ([#1614](https://github.com/evanw/esbuild/issues/1614)) Some background: esbuild's bundler emulates a CommonJS environment. The bundling process replaces the literal syntax `require()` with the referenced module at compile-time. However, other uses of `require` such as `require(someFunction())` are not bundled since the value of `someFunction()` depends on code evaluation, and esbuild does not evaluate code at compile-time. So it's possible for some references to `require` to remain after bundling. diff --git a/internal/bundler/bundler_ts_test.go b/internal/bundler/bundler_ts_test.go index 4bb718cdf43..aaddbc97639 100644 --- a/internal/bundler/bundler_ts_test.go +++ b/internal/bundler/bundler_ts_test.go @@ -1194,3 +1194,53 @@ func TestTSComputedClassFieldUseDefineTrueLower(t *testing.T) { }, }) } + +func TestTSAbstractClassFieldUseAssign(t *testing.T) { + default_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", + UseDefineForClassFields: config.False, + }, + }) +} + +func TestTSAbstractClassFieldUseDefine(t *testing.T) { + default_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", + UseDefineForClassFields: config.True, + }, + }) +} diff --git a/internal/bundler/snapshots/snapshots_default.txt b/internal/bundler/snapshots/snapshots_default.txt index aaa36b44d95..f0ad18104e7 100644 --- a/internal/bundler/snapshots/snapshots_default.txt +++ b/internal/bundler/snapshots/snapshots_default.txt @@ -2579,6 +2579,25 @@ switch (bar) { let a; } +================================================================================ +TestTSAbstractClassFieldUseAssign +---------- /out.js ---------- +const keepThis = Symbol("keepThis"); +class Foo { +} +keepThis; +(() => new Foo())(); + +================================================================================ +TestTSAbstractClassFieldUseDefine +---------- /out.js ---------- +const keepThisToo = Symbol("keepThisToo"); +class Foo { + keepThis; + [keepThisToo]; +} +(() => new Foo())(); + ================================================================================ TestTSComputedClassFieldUseDefineFalse ---------- /out.js ---------- diff --git a/internal/js_parser/js_parser.go b/internal/js_parser/js_parser.go index 64c19a469dc..682b4fbb1ba 100644 --- a/internal/js_parser/js_parser.go +++ b/internal/js_parser/js_parser.go @@ -1809,6 +1809,7 @@ type propertyOpts struct { // Class-related options isStatic bool isTSDeclare bool + isTSAbstract bool isClass bool classHasExtends bool allowTSDecorators bool @@ -1936,7 +1937,16 @@ func (p *parser) parseProperty(kind js_ast.PropertyKind, opts propertyOpts, erro return js_ast.Property{}, false } - case "private", "protected", "public", "readonly", "abstract", "override": + case "abstract": + if opts.isClass && p.options.ts.Parse && !opts.isTSAbstract && raw == name { + opts.isTSAbstract = true + scopeIndex := len(p.scopesInOrder) + p.parseProperty(kind, opts, nil) + p.discardScopesUpTo(scopeIndex) + return js_ast.Property{}, false + } + + case "private", "protected", "public", "readonly", "override": // Skip over TypeScript keywords if opts.isClass && p.options.ts.Parse && raw == name { return p.parseProperty(kind, opts, nil)