Skip to content

Commit

Permalink
formalize and expose the ToString operation
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Oct 18, 2023
1 parent 24f87d2 commit 4e11b50
Showing 1 changed file with 57 additions and 49 deletions.
106 changes: 57 additions & 49 deletions internal/js_ast/js_ast_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -981,6 +981,51 @@ func ToNumberWithoutSideEffects(data E) (float64, bool) {
return 0, false
}

func ToStringWithoutSideEffects(data E) (string, bool) {
switch e := data.(type) {
case *ENull:
return "null", true

case *EUndefined:
return "undefined", true

case *EBoolean:
if e.Value {
return "true", true
} else {
return "false", true
}

case *EBigInt:
// Only do this if there is no radix
if len(e.Value) < 2 || e.Value[0] != '0' {
return e.Value, true
}

case *ENumber:
if str, ok := TryToStringOnNumberSafely(e.Value, 10); ok {
return str, true
}

case *ERegExp:
return e.Value, true

case *EDot:
// This is dumb but some JavaScript obfuscators use this to generate string literals
if e.Name == "constructor" {
switch e.Target.Data.(type) {
case *EString:
return "function String() { [native code] }", true

case *ERegExp:
return "function RegExp() { [native code] }", true
}
}
}

return "", false
}

func extractNumericValue(data E) (float64, bool) {
switch e := data.(type) {
case *EAnnotation:
Expand Down Expand Up @@ -1490,7 +1535,9 @@ func foldAdditionPreProcess(expr Expr) Expr {
items = append(items, "")
continue
}
item = stringAdditionOperandToString(item)
if str, ok := ToStringWithoutSideEffects(item.Data); ok {
item.Data = &EString{Value: helpers.StringToUTF16(str)}
}
str, ok := item.Data.(*EString)
if !ok {
break
Expand All @@ -1510,51 +1557,6 @@ func foldAdditionPreProcess(expr Expr) Expr {
return expr
}

// Note: We know that this is string addition when we get here
func stringAdditionOperandToString(expr Expr) Expr {
switch e := expr.Data.(type) {
case *ENull:
expr.Data = &EString{Value: helpers.StringToUTF16("null")}

case *EUndefined:
expr.Data = &EString{Value: helpers.StringToUTF16("undefined")}

case *EBoolean:
if e.Value {
expr.Data = &EString{Value: helpers.StringToUTF16("true")}
} else {
expr.Data = &EString{Value: helpers.StringToUTF16("false")}
}

case *EBigInt:
// Only do this if there is no radix
if len(e.Value) < 2 || e.Value[0] != '0' {
expr.Data = &EString{Value: helpers.StringToUTF16(e.Value)}
}

case *ENumber:
if str, ok := TryToStringOnNumberSafely(e.Value, 10); ok {
expr.Data = &EString{Value: helpers.StringToUTF16(str)}
}

case *ERegExp:
expr.Data = &EString{Value: helpers.StringToUTF16(e.Value)}

case *EDot:
// This is dumb but some JavaScript obfuscators use this to generate string literals
if e.Name == "constructor" {
switch e.Target.Data.(type) {
case *EString:
expr.Data = &EString{Value: helpers.StringToUTF16("function String() { [native code] }")}

case *ERegExp:
expr.Data = &EString{Value: helpers.StringToUTF16("function RegExp() { [native code] }")}
}
}
}
return expr
}

type StringAdditionKind uint8

const (
Expand All @@ -1577,14 +1579,18 @@ func FoldStringAddition(left Expr, right Expr, kind StringAdditionKind) Expr {
if kind != StringAdditionWithNestedLeft {
switch right.Data.(type) {
case *EString, *ETemplate:
left = stringAdditionOperandToString(left)
if str, ok := ToStringWithoutSideEffects(left.Data); ok {
left.Data = &EString{Value: helpers.StringToUTF16(str)}
}
}
}

switch l := left.Data.(type) {
case *EString:
// "'x' + 0" => "'x' + '0'"
right = stringAdditionOperandToString(right)
if str, ok := ToStringWithoutSideEffects(right.Data); ok {
right.Data = &EString{Value: helpers.StringToUTF16(str)}
}

switch r := right.Data.(type) {
case *EString:
Expand Down Expand Up @@ -1613,7 +1619,9 @@ func FoldStringAddition(left Expr, right Expr, kind StringAdditionKind) Expr {
case *ETemplate:
if l.TagOrNil.Data == nil {
// "`${x}` + 0" => "`${x}` + '0'"
right = stringAdditionOperandToString(right)
if str, ok := ToStringWithoutSideEffects(right.Data); ok {
right.Data = &EString{Value: helpers.StringToUTF16(str)}
}

switch r := right.Data.(type) {
case *EString:
Expand Down

0 comments on commit 4e11b50

Please sign in to comment.