Skip to content

Commit

Permalink
known global identifiers don't have side effects
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Jul 6, 2020
1 parent 6a98c84 commit eddb21d
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 14 deletions.
13 changes: 11 additions & 2 deletions internal/ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,8 @@ type EDot struct {
NameLoc Loc
OptionalChain OptionalChain

// If true, this property access is known to be free of side-effects
// If true, this property access is known to be free of side-effects. That
// means it can be removed if the resulting value isn't used.
CanBeRemovedIfUnused bool
}

Expand All @@ -467,7 +468,15 @@ type EFunction struct{ Fn Fn }

type EClass struct{ Class Class }

type EIdentifier struct{ Ref Ref }
type EIdentifier struct {
Ref Ref

// If true, this identifier is known to not have a side effect (i.e. to not
// throw an exception) when referenced. If false, this identifier may or may
// not have side effects when referenced. This is used to allow the removal
// of known globals such as "Object" if they aren't used.
CanBeRemovedIfUnused bool
}

// This is similar to an EIdentifier but it represents a reference to an ES6
// import item.
Expand Down
16 changes: 15 additions & 1 deletion internal/config/globals.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ import (

var processedGlobals *ProcessedDefines
var knownGlobals = [][]string{
// These global identifiers should exist in all JavaScript environments
{"Array"},
{"Boolean"},
{"Function"},
{"Math"},
{"Number"},
{"Object"},
{"RegExp"},
{"String"},

// Object: Static methods
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object#Static_methods
{"Object", "assign"},
Expand Down Expand Up @@ -143,7 +153,11 @@ func ProcessDefines(userDefines map[string]DefineData) ProcessedDefines {
// exception if "a.b" is undefined.
for _, parts := range knownGlobals {
tail := parts[len(parts)-1]
result.DotDefines[tail] = append(result.DotDefines[tail], DotDefine{Parts: parts})
if len(parts) == 1 {
result.IdentifierDefines[tail] = DefineData{}
} else {
result.DotDefines[tail] = append(result.DotDefines[tail], DotDefine{Parts: parts})
}
}

// Swap in certain literal values because those can be constant folded
Expand Down
26 changes: 15 additions & 11 deletions internal/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -6667,14 +6667,20 @@ func (p *parser) visitExprInOut(expr ast.Expr, in exprIn) (ast.Expr, exprOut) {

// Substitute user-specified defines for unbound symbols
if p.symbols[e.Ref.InnerIndex].Kind == ast.SymbolUnbound && !result.isInsideWithScope {
if data, ok := p.Defines.IdentifierDefines[name]; ok && data.DefineFunc != nil {
new := p.valueForDefine(expr.Loc, in.assignTarget, data.DefineFunc)

// Don't substitute an identifier for a non-identifier if this is an
// assignment target, since it'll cause a syntax error
if _, ok := new.Data.(*ast.EIdentifier); in.assignTarget == ast.AssignTargetNone || ok {
return new, exprOut{}
if data, ok := p.Defines.IdentifierDefines[name]; ok {
if data.DefineFunc != nil {
new := p.valueForDefine(expr.Loc, in.assignTarget, data.DefineFunc)

// Don't substitute an identifier for a non-identifier if this is an
// assignment target, since it'll cause a syntax error
if _, ok := new.Data.(*ast.EIdentifier); in.assignTarget == ast.AssignTargetNone || ok {
return new, exprOut{}
}
}

// All identifier defines that don't have user-specified replacements
// are known to be side-effect free. Mark them as such if we get here.
e.CanBeRemovedIfUnused = true
}
}

Expand Down Expand Up @@ -7979,8 +7985,7 @@ func (p *parser) exprCanBeRemovedIfUnused(expr ast.Expr) bool {
return p.classCanBeRemovedIfUnused(e.Class)

case *ast.EIdentifier:
symbol := p.symbols[e.Ref.InnerIndex]
if symbol.Kind != ast.SymbolUnbound {
if e.CanBeRemovedIfUnused || p.symbols[e.Ref.InnerIndex].Kind != ast.SymbolUnbound {
return true
}

Expand Down Expand Up @@ -8048,8 +8053,7 @@ func (p *parser) simplifyUnusedExpr(expr ast.Expr) ast.Expr {
}

case *ast.EIdentifier:
symbol := p.symbols[e.Ref.InnerIndex]
if symbol.Kind != ast.SymbolUnbound {
if e.CanBeRemovedIfUnused || p.symbols[e.Ref.InnerIndex].Kind != ast.SymbolUnbound {
return ast.Expr{}
}

Expand Down
4 changes: 4 additions & 0 deletions internal/parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1363,6 +1363,10 @@ func TestMangleUnused(t *testing.T) {
expectPrintedMangle(t, "(() => {})", "")
expectPrintedMangle(t, "import.meta", "")

// Known globals can be removed
expectPrintedMangle(t, "Object", "")
expectPrintedMangle(t, "NonObject", "NonObject;\n")

expectPrintedMangle(t, "var bound; unbound", "var bound;\nunbound;\n")
expectPrintedMangle(t, "var bound; bound", "var bound;\n")
expectPrintedMangle(t, "foo, 123, bar", "foo, bar;\n")
Expand Down

0 comments on commit eddb21d

Please sign in to comment.