Skip to content

Commit

Permalink
minify now unwraps inlined enum property keys
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Jun 5, 2023
1 parent c6fdc7f commit 76fb8bc
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 5 deletions.
73 changes: 73 additions & 0 deletions internal/bundler_tests/bundler_ts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,79 @@ func TestTSMinifyDerivedClass(t *testing.T) {
})
}

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 TestTSImportVsLocalCollisionAllTypes(t *testing.T) {
ts_suite.expectBundled(t, bundled{
files: map[string]string{
Expand Down
54 changes: 54 additions & 0 deletions internal/bundler_tests/snapshots/snapshots_ts.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1180,6 +1180,60 @@ var Foo=(e=>(e[e.A=0]="A",e[e.B=1]="B",e[e.C=e]="C",e))(Foo||{});
---------- /b.js ----------
export var Foo=(e=>(e[e.X=0]="X",e[e.Y=1]="Y",e[e.Z=e]="Z",e))(Foo||{});

================================================================================
TestTSMinifyEnumPropertyNames
---------- /out.js ----------
// entry.ts
var Foo = class {
100 = 100;
200 = 200;
300 = 300;
"str 1" = "str 1" /* STR */;
123 = 123 /* NUM */;
"str 2" = "str 2" /* STR */;
321 = 321 /* NUM */;
};
shouldNotBeComputed(
class {
100 = 100;
200 = 200;
300 = 300;
"str 1" = "str 1" /* STR */;
123 = 123 /* NUM */;
"str 2" = "str 2" /* STR */;
321 = 321 /* NUM */;
},
{
100: 100,
200: 200,
300: 300,
"str 1": "str 1" /* STR */,
123: 123 /* NUM */,
"str 2": "str 2" /* STR */,
321: 321 /* NUM */
}
);
mustBeComputed(
{ ["__proto__"]: null },
{ ["__proto__"]: null },
class {
["constructor"]() {
}
},
class {
["constructor"]() {
}
},
class {
static ["prototype"]() {
}
},
class {
static ["prototype"]() {
}
}
);

================================================================================
TestTSMinifyNamespace
---------- /a.js ----------
Expand Down
33 changes: 28 additions & 5 deletions internal/js_parser/js_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -10893,15 +10893,26 @@ func (p *parser) visitClass(nameScopeLoc logger.Loc, class *js_ast.Class, defaul
property.Key = key

if p.options.minifySyntax {
if str, ok := key.Data.(*js_ast.EString); ok {
if numberValue, ok := js_ast.StringToEquivalentNumberValue(str.Value); ok && numberValue >= 0 {
if inlined, ok := key.Data.(*js_ast.EInlinedEnum); ok {
switch inlined.Value.Data.(type) {
case *js_ast.EString, *js_ast.ENumber:
key.Data = inlined.Value.Data
property.Key.Data = key.Data
}
}
switch k := key.Data.(type) {
case *js_ast.ENumber:
// "class { [123] }" => "class { 123 }"
property.Flags &= ^js_ast.PropertyIsComputed
case *js_ast.EString:
if numberValue, ok := js_ast.StringToEquivalentNumberValue(k.Value); ok && numberValue >= 0 {
// "class { '123' }" => "class { 123 }"
property.Key.Data = &js_ast.ENumber{Value: numberValue}
property.Flags &= ^js_ast.PropertyIsComputed
} else if property.Flags.Has(js_ast.PropertyIsComputed) {
// "class {['x'] = y}" => "class {'x' = y}"
isInvalidConstructor := false
if helpers.UTF16EqualsString(str.Value, "constructor") {
if helpers.UTF16EqualsString(k.Value, "constructor") {
if !property.Flags.Has(js_ast.PropertyIsMethod) {
// "constructor" is an invalid name for both instance and static fields
isInvalidConstructor = true
Expand All @@ -10912,7 +10923,7 @@ func (p *parser) visitClass(nameScopeLoc logger.Loc, class *js_ast.Class, defaul
}

// A static property must not be called "prototype"
isInvalidPrototype := property.Flags.Has(js_ast.PropertyIsStatic) && helpers.UTF16EqualsString(str.Value, "prototype")
isInvalidPrototype := property.Flags.Has(js_ast.PropertyIsStatic) && helpers.UTF16EqualsString(k.Value, "prototype")

if !isInvalidConstructor && !isInvalidPrototype {
property.Flags &= ^js_ast.PropertyIsComputed
Expand Down Expand Up @@ -13276,8 +13287,20 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO

// "{['x']: y}" => "{x: y}"
if p.options.minifySyntax && property.Flags.Has(js_ast.PropertyIsComputed) {
if str, ok := key.Data.(*js_ast.EString); ok && js_ast.IsIdentifierUTF16(str.Value) && !helpers.UTF16EqualsString(str.Value, "__proto__") {
if inlined, ok := key.Data.(*js_ast.EInlinedEnum); ok {
switch inlined.Value.Data.(type) {
case *js_ast.EString, *js_ast.ENumber:
key.Data = inlined.Value.Data
property.Key.Data = key.Data
}
}
switch k := key.Data.(type) {
case *js_ast.ENumber:
property.Flags &= ^js_ast.PropertyIsComputed
case *js_ast.EString:
if !helpers.UTF16EqualsString(k.Value, "__proto__") {
property.Flags &= ^js_ast.PropertyIsComputed
}
}
}
} else {
Expand Down
28 changes: 28 additions & 0 deletions internal/js_printer/js_printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -982,6 +982,34 @@ func (p *printer) printProperty(property js_ast.Property) {
return
}

// Handle key syntax compression for cross-module constant inlining of enums
if p.options.MinifySyntax && property.Flags.Has(js_ast.PropertyIsComputed) {
if dot, ok := property.Key.Data.(*js_ast.EDot); ok {
if id, ok := dot.Target.Data.(*js_ast.EImportIdentifier); ok {
ref := js_ast.FollowSymbols(p.symbols, id.Ref)
if symbol := p.symbols.Get(ref); symbol.Kind == js_ast.SymbolTSEnum {
if enum, ok := p.options.TSEnums[ref]; ok {
if value, ok := enum[dot.Name]; ok {
if value.String != nil {
property.Key.Data = &js_ast.EString{Value: value.String}

// Problematic key names must stay computed for correctness
if !helpers.UTF16EqualsString(value.String, "__proto__") &&
!helpers.UTF16EqualsString(value.String, "constructor") &&
!helpers.UTF16EqualsString(value.String, "prototype") {
property.Flags &= ^js_ast.PropertyIsComputed
}
} else {
property.Key.Data = &js_ast.ENumber{Value: value.Number}
property.Flags &= ^js_ast.PropertyIsComputed
}
}
}
}
}
}
}

if property.Flags.Has(js_ast.PropertyIsStatic) {
p.addSourceMapping(property.Loc)
p.print("static")
Expand Down

0 comments on commit 76fb8bc

Please sign in to comment.