Skip to content

Commit

Permalink
implement pass-through for private names (#47)
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Jun 6, 2020
1 parent 88cbe7f commit 38f559e
Show file tree
Hide file tree
Showing 12 changed files with 468 additions and 113 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,18 @@

## Unreleased

* Initial support for private names ([#47](https://github.com/evanw/esbuild/issues/47))

Private names are an access control mechanism for classes. They begin with a `#` and are not accessible outside of the class they are declared in. The private name syntax can now be parsed, printed, and minified correctly. Transforming this syntax for older browsers is not supported yet. This is what the syntax looks like:

```js
class Counter {
#count = 1
get value() { return this.#count }
increment() { this.#count++ }
}
```

* Data loaders now set "no side effects"

Files loaded using the `json`, `text`, `base64`, `dataurl`, and `file` loaders are now removed from the bundle if the files that import them never use the imports. This is the same behavior as the `"sideEffects": false` setting in `package.json`.
Expand Down
31 changes: 16 additions & 15 deletions README.md
Expand Up @@ -104,24 +104,25 @@ These syntax features are conditionally transformed for older browsers depending

These syntax features are currently always passed through un-transformed:

| Syntax transform | Unsupported when `--target` is below | Example |
|----------------------------------------------------------------------------|--------------------------------------|-----------------------------|
| [Async functions](https://github.com/tc39/ecmascript-asyncawait) | `es2017` | `async () => {}` |
| [Rest properties](https://github.com/tc39/proposal-object-rest-spread) | `es2018` | `let {...x} = y` |
| [Asynchronous iteration](https://github.com/tc39/proposal-async-iteration) | `es2018` | `for await (let x of y) {}` |
| [Async generators](https://github.com/tc39/proposal-async-iteration) | `es2018` | `async function* foo() {}` |
| [BigInt](https://github.com/tc39/proposal-bigint) | `es2020` | `123n` |
| [Hashbang grammar](https://github.com/tc39/proposal-hashbang) | `esnext` | `#!/usr/bin/env node` |
| Syntax transform | Unsupported when `--target` is below | Example |
|-----------------------------------------------------------------------------------|--------------------------------------|-----------------------------|
| [Async functions](https://github.com/tc39/ecmascript-asyncawait) | `es2017` | `async () => {}` |
| [Rest properties](https://github.com/tc39/proposal-object-rest-spread) | `es2018` | `let {...x} = y` |
| [Asynchronous iteration](https://github.com/tc39/proposal-async-iteration) | `es2018` | `for await (let x of y) {}` |
| [Async generators](https://github.com/tc39/proposal-async-iteration) | `es2018` | `async function* foo() {}` |
| [BigInt](https://github.com/tc39/proposal-bigint) | `es2020` | `123n` |
| [Hashbang grammar](https://github.com/tc39/proposal-hashbang) | `esnext` | `#!/usr/bin/env node` |
| [Private instance methods](https://github.com/tc39/proposal-private-methods) | `esnext` | `class { #x() {} }` |
| [Private instance fields](https://github.com/tc39/proposal-class-fields) | `esnext` | `class { #x }` |
| [Private static methods](https://github.com/tc39/proposal-static-class-features/) | `esnext` | `class { static #x() {} }` |
| [Private static fields](https://github.com/tc39/proposal-static-class-features/) | `esnext` | `class { static #x }` |

These syntax features are not yet supported, and currently cannot be parsed:

| Syntax transform | Language version | Example |
|----------------------------------------------------------------------------------------------|------------------|-----------------------|
| [Private instance methods](https://github.com/tc39/proposal-private-methods) | `esnext` | `class { #x() {} }` |
| [Private instance fields](https://github.com/tc39/proposal-class-fields) | `esnext` | `class { #x }` |
| [Private static fields and methods](https://github.com/tc39/proposal-static-class-features/) | `esnext` | `class { static #x }` |
| [Logical assignment operators](https://github.com/tc39/proposal-logical-assignment) | `esnext` | `a ??= b` |
| [Top-level await](https://github.com/tc39/proposal-top-level-await) | `esnext` | `await import(x)` |
| Syntax transform | Language version | Example |
|-------------------------------------------------------------------------------------|------------------|-------------------|
| [Logical assignment operators](https://github.com/tc39/proposal-logical-assignment) | `esnext` | `a ??= b` |
| [Top-level await](https://github.com/tc39/proposal-top-level-await) | `esnext` | `await import(x)` |

See also [the list of finished ECMAScript proposals](https://github.com/tc39/proposals/blob/master/finished-proposals.md) and [the list of active ECMAScript proposals](https://github.com/tc39/proposals/blob/master/README.md).

Expand Down
79 changes: 46 additions & 33 deletions internal/ast/ast.go
Expand Up @@ -304,6 +304,7 @@ type FnBody struct {
type Class struct {
Name *LocRef
Extends *Expr
BodyLoc Loc
Properties []Property
}

Expand Down Expand Up @@ -456,6 +457,13 @@ type EImportIdentifier struct {
Ref Ref
}

// This is similar to EIdentifier but it represents class-private fields and
// methods. It can be used where computed properties can be used, such as
// EIndex and Property.
type EPrivateIdentifier struct {
Ref Ref
}

type EJSXElement struct {
Tag *Expr
Properties []Property
Expand Down Expand Up @@ -516,39 +524,40 @@ type EImport struct {
Expr Expr
}

func (*EArray) isExpr() {}
func (*EUnary) isExpr() {}
func (*EBinary) isExpr() {}
func (*EBoolean) isExpr() {}
func (*ESuper) isExpr() {}
func (*ENull) isExpr() {}
func (*EUndefined) isExpr() {}
func (*EThis) isExpr() {}
func (*ENew) isExpr() {}
func (*ENewTarget) isExpr() {}
func (*EImportMeta) isExpr() {}
func (*ECall) isExpr() {}
func (*EDot) isExpr() {}
func (*EIndex) isExpr() {}
func (*EArrow) isExpr() {}
func (*EFunction) isExpr() {}
func (*EClass) isExpr() {}
func (*EIdentifier) isExpr() {}
func (*EImportIdentifier) isExpr() {}
func (*EJSXElement) isExpr() {}
func (*EMissing) isExpr() {}
func (*ENumber) isExpr() {}
func (*EBigInt) isExpr() {}
func (*EObject) isExpr() {}
func (*ESpread) isExpr() {}
func (*EString) isExpr() {}
func (*ETemplate) isExpr() {}
func (*ERegExp) isExpr() {}
func (*EAwait) isExpr() {}
func (*EYield) isExpr() {}
func (*EIf) isExpr() {}
func (*ERequire) isExpr() {}
func (*EImport) isExpr() {}
func (*EArray) isExpr() {}
func (*EUnary) isExpr() {}
func (*EBinary) isExpr() {}
func (*EBoolean) isExpr() {}
func (*ESuper) isExpr() {}
func (*ENull) isExpr() {}
func (*EUndefined) isExpr() {}
func (*EThis) isExpr() {}
func (*ENew) isExpr() {}
func (*ENewTarget) isExpr() {}
func (*EImportMeta) isExpr() {}
func (*ECall) isExpr() {}
func (*EDot) isExpr() {}
func (*EIndex) isExpr() {}
func (*EArrow) isExpr() {}
func (*EFunction) isExpr() {}
func (*EClass) isExpr() {}
func (*EIdentifier) isExpr() {}
func (*EImportIdentifier) isExpr() {}
func (*EPrivateIdentifier) isExpr() {}
func (*EJSXElement) isExpr() {}
func (*EMissing) isExpr() {}
func (*ENumber) isExpr() {}
func (*EBigInt) isExpr() {}
func (*EObject) isExpr() {}
func (*ESpread) isExpr() {}
func (*EString) isExpr() {}
func (*ETemplate) isExpr() {}
func (*ERegExp) isExpr() {}
func (*EAwait) isExpr() {}
func (*EYield) isExpr() {}
func (*EIf) isExpr() {}
func (*ERequire) isExpr() {}
func (*EImport) isExpr() {}

func JoinWithComma(a Expr, b Expr) Expr {
return Expr{a.Loc, &EBinary{BinOpComma, a, b}}
Expand Down Expand Up @@ -881,6 +890,9 @@ const (
// Classes can merge with TypeScript namespaces.
SymbolClass

// A class-private identifier (i.e. "#foo").
SymbolPrivate

// TypeScript enums can merge with TypeScript namespaces and other TypeScript
// enums.
SymbolTSEnum
Expand Down Expand Up @@ -999,6 +1011,7 @@ const (
ScopeWith
ScopeLabel
ScopeClassName
ScopeClassBody

// The scopes below stop hoisted variables from extending into parent scopes
ScopeEntry // This is a module, TypeScript enum, or TypeScript namespace
Expand Down
97 changes: 97 additions & 0 deletions internal/bundler/bundler_test.go
Expand Up @@ -4719,3 +4719,100 @@ func TestManyEntryPoints(t *testing.T) {
},
})
}

func TestRenamePrivateIdentifiersNoBundle(t *testing.T) {
expectBundled(t, bundled{
files: map[string]string{
"/entry.js": `
class Foo {
#foo
foo = class {
#foo
#foo2
}
}
class Bar {
#foo
foo = class {
#foo2
#foo
}
}
`,
},
entryPaths: []string{"/entry.js"},
parseOptions: parser.ParseOptions{
IsBundling: false,
},
bundleOptions: BundleOptions{
IsBundling: false,
AbsOutputFile: "/out.js",
},
expected: map[string]string{
"/out.js": `class Foo {
#foo;
foo = class {
#foo2;
#foo22;
};
}
class Bar {
#foo;
foo = class {
#foo2;
#foo3;
};
}
`,
},
})
}

func TestMinifyPrivateIdentifiersNoBundle(t *testing.T) {
expectBundled(t, bundled{
files: map[string]string{
"/entry.js": `
class Foo {
#foo
foo = class {
#foo
#foo2
}
}
class Bar {
#foo
foo = class {
#foo2
#foo
}
}
`,
},
entryPaths: []string{"/entry.js"},
parseOptions: parser.ParseOptions{
IsBundling: false,
},
bundleOptions: BundleOptions{
IsBundling: false,
MinifyIdentifiers: true,
AbsOutputFile: "/out.js",
},
expected: map[string]string{
"/out.js": `class Foo {
#a;
foo = class {
#b;
#c;
};
}
class Bar {
#a;
foo = class {
#b;
#c;
};
}
`,
},
})
}

0 comments on commit 38f559e

Please sign in to comment.