package js import ( "bytes" "fmt" "io" "strconv" "strings" "github.com/tdewolff/parse/v2" ) var ErrInvalidJSON = fmt.Errorf("invalid JSON") type JSONer interface { JSON(io.Writer) error } // AST is the full ECMAScript abstract syntax tree. type AST struct { BlockStmt // module } func (ast AST) String() string { s := "" for i, item := range ast.BlockStmt.List { if i != 0 { s += " " } s += item.String() } return s } // JS writes JavaScript to writer. func (ast AST) JS(w io.Writer) { for i, item := range ast.List { if i != 0 { w.Write([]byte("\n")) } item.JS(w) if _, ok := item.(*VarDecl); ok { w.Write([]byte(";")) } } } // JSONString returns a string of JavaScript. func (ast AST) JSString() string { sb := strings.Builder{} ast.JS(&sb) return sb.String() } // JSON writes JSON to writer. func (ast AST) JSON(w io.Writer) error { if 1 < len(ast.List) { return ErrInvalidJSON } else if len(ast.List) == 0 { return nil } else if expr, ok := ast.List[0].(*ExprStmt); !ok { return ErrInvalidJSON } else if val, ok := expr.Value.(JSONer); !ok { return ErrInvalidJSON } else { return val.JSON(w) } return nil } // JSONString returns a string of JSON if valid. func (ast AST) JSONString() (string, error) { sb := strings.Builder{} err := ast.JSON(&sb) return sb.String(), err } //////////////////////////////////////////////////////////////// // DeclType specifies the kind of declaration. type DeclType uint16 // DeclType values. const ( NoDecl DeclType = iota // undeclared variables VariableDecl // var FunctionDecl // function ArgumentDecl // function and method arguments LexicalDecl // let, const, class CatchDecl // catch statement argument ExprDecl // function expression name or class expression name ) func (decl DeclType) String() string { switch decl { case NoDecl: return "NoDecl" case VariableDecl: return "VariableDecl" case FunctionDecl: return "FunctionDecl" case ArgumentDecl: return "ArgumentDecl" case LexicalDecl: return "LexicalDecl" case CatchDecl: return "CatchDecl" case ExprDecl: return "ExprDecl" } return "Invalid(" + strconv.Itoa(int(decl)) + ")" } // Var is a variable, where Decl is the type of declaration and can be var|function for function scoped variables, let|const|class for block scoped variables. type Var struct { Data []byte Link *Var // is set when merging variable uses, as in: {a} {var a} where the first links to the second, only used for undeclared variables Uses uint16 Decl DeclType } // Name returns the variable name. func (v *Var) Name() []byte { for v.Link != nil { v = v.Link } return v.Data } func (v *Var) Info() string { s := fmt.Sprintf("%p type=%s name='%s' uses=%d", v, v.Decl, string(v.Data), v.Uses) links := 0 for v.Link != nil { v = v.Link links++ } if 0 < links { s += fmt.Sprintf(" links=%d => %p", links, v) } return s } func (v Var) String() string { return string(v.Name()) } // JS writes JavaScript to writer. func (v Var) JS(w io.Writer) { w.Write(v.Name()) } // VarsByUses is sortable by uses in descending order. type VarsByUses VarArray func (vs VarsByUses) Len() int { return len(vs) } func (vs VarsByUses) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] } func (vs VarsByUses) Less(i, j int) bool { return vs[i].Uses > vs[j].Uses } //////////////////////////////////////////////////////////////// // VarArray is a set of variables in scopes. type VarArray []*Var func (vs VarArray) String() string { s := "[" for i, v := range vs { if i != 0 { s += ", " } links := 0 for v.Link != nil { v = v.Link links++ } s += fmt.Sprintf("Var{%v %s %v %v}", v.Decl, string(v.Data), links, v.Uses) } return s + "]" } // Scope is a function or block scope with a list of variables declared and used. type Scope struct { Parent, Func *Scope // Parent is nil for global scope Declared VarArray // Link in Var are always nil Undeclared VarArray VarDecls []*VarDecl NumForDecls uint16 // offset into Declared to mark variables used in for statements NumFuncArgs uint16 // offset into Declared to mark variables used in function arguments NumArgUses uint16 // offset into Undeclared to mark variables used in arguments IsGlobalOrFunc bool HasWith bool } func (s Scope) String() string { return "Scope{Declared: " + s.Declared.String() + ", Undeclared: " + s.Undeclared.String() + "}" } // Declare declares a new variable. func (s *Scope) Declare(decl DeclType, name []byte) (*Var, bool) { // refer to new variable for previously undeclared symbols in the current and lower scopes // this happens in `{ a = 5; } var a` where both a's refer to the same variable curScope := s if decl == VariableDecl || decl == FunctionDecl { // find function scope for var and function declarations for s != s.Func { // make sure that `{let i;{var i}}` is an error if v := s.findDeclared(name, false); v != nil && v.Decl != decl && v.Decl != CatchDecl { return nil, false } s = s.Parent } } if v := s.findDeclared(name, true); v != nil { // variable already declared, might be an error or a duplicate declaration if (ArgumentDecl < v.Decl || FunctionDecl < decl) && v.Decl != ExprDecl { // only allow (v.Decl,decl) of: (var|function|argument,var|function), (expr,*), any other combination is a syntax error return nil, false } if v.Decl == ExprDecl { v.Decl = decl } v.Uses++ for s != curScope { curScope.AddUndeclared(v) // add variable declaration as used variable to the current scope curScope = curScope.Parent } return v, true } var v *Var // reuse variable if previously used, as in: a;var a if decl != ArgumentDecl { // in case of function f(a=b,b), where the first b is different from the second for i, uv := range s.Undeclared[s.NumArgUses:] { // no need to evaluate v.Link as v.Data stays the same and Link is nil in the active scope if 0 < uv.Uses && uv.Decl == NoDecl && bytes.Equal(name, uv.Data) { // must be NoDecl so that it can't be a var declaration that has been added v = uv s.Undeclared = append(s.Undeclared[:int(s.NumArgUses)+i], s.Undeclared[int(s.NumArgUses)+i+1:]...) break } } } if v == nil { // add variable to the context list and to the scope v = &Var{name, nil, 0, decl} } else { v.Decl = decl } v.Uses++ s.Declared = append(s.Declared, v) for s != curScope { curScope.AddUndeclared(v) // add variable declaration as used variable to the current scope curScope = curScope.Parent } return v, true } // Use increments the usage of a variable. func (s *Scope) Use(name []byte) *Var { // check if variable is declared in the current scope v := s.findDeclared(name, false) if v == nil { // check if variable is already used before in the current or lower scopes v = s.findUndeclared(name) if v == nil { // add variable to the context list and to the scope's undeclared v = &Var{name, nil, 0, NoDecl} s.Undeclared = append(s.Undeclared, v) } } v.Uses++ return v } // findDeclared finds a declared variable in the current scope. func (s *Scope) findDeclared(name []byte, skipForDeclared bool) *Var { start := 0 if skipForDeclared { // we skip the for initializer for declarations (only has effect for let/const) start = int(s.NumForDecls) } // reverse order to find the inner let first in `for(let a in []){let a; {a}}` for i := len(s.Declared) - 1; start <= i; i-- { v := s.Declared[i] // no need to evaluate v.Link as v.Data stays the same, and Link is always nil in Declared if bytes.Equal(name, v.Data) { return v } } return nil } // findUndeclared finds an undeclared variable in the current and contained scopes. func (s *Scope) findUndeclared(name []byte) *Var { for _, v := range s.Undeclared { // no need to evaluate v.Link as v.Data stays the same and Link is nil in the active scope if 0 < v.Uses && bytes.Equal(name, v.Data) { return v } } return nil } // add undeclared variable to scope, this is called for the block scope when declaring a var in it func (s *Scope) AddUndeclared(v *Var) { // don't add undeclared symbol if it's already there for _, vorig := range s.Undeclared { if v == vorig { return } } s.Undeclared = append(s.Undeclared, v) // add variable declaration as used variable to the current scope } // MarkForStmt marks the declared variables in current scope as for statement initializer to distinguish from declarations in body. func (s *Scope) MarkForStmt() { s.NumForDecls = uint16(len(s.Declared)) s.NumArgUses = uint16(len(s.Undeclared)) // ensures for different b's in for(var a in b){let b} } // MarkFuncArgs marks the declared/undeclared variables in the current scope as function arguments. func (s *Scope) MarkFuncArgs() { s.NumFuncArgs = uint16(len(s.Declared)) s.NumArgUses = uint16(len(s.Undeclared)) // ensures different b's in `function f(a=b){var b}`. } // HoistUndeclared copies all undeclared variables of the current scope to the parent scope. func (s *Scope) HoistUndeclared() { for i, vorig := range s.Undeclared { // no need to evaluate vorig.Link as vorig.Data stays the same if 0 < vorig.Uses && vorig.Decl == NoDecl { if v := s.Parent.findDeclared(vorig.Data, false); v != nil { // check if variable is declared in parent scope v.Uses += vorig.Uses vorig.Link = v s.Undeclared[i] = v // point reference to existing var (to avoid many Link chains) } else if v := s.Parent.findUndeclared(vorig.Data); v != nil { // check if variable is already used before in parent scope v.Uses += vorig.Uses vorig.Link = v s.Undeclared[i] = v // point reference to existing var (to avoid many Link chains) } else { // add variable to the context list and to the scope's undeclared s.Parent.Undeclared = append(s.Parent.Undeclared, vorig) } } } } // UndeclareScope undeclares all declared variables in the current scope and adds them to the parent scope. // Called when possible arrow func ends up being a parenthesized expression, scope is not further used. func (s *Scope) UndeclareScope() { // look if the variable already exists in the parent scope, if so replace the Var pointer in original use for _, vorig := range s.Declared { // no need to evaluate vorig.Link as vorig.Data stays the same, and Link is always nil in Declared // vorig.Uses will be atleast 1 if v := s.Parent.findDeclared(vorig.Data, false); v != nil { // check if variable has been declared in this scope v.Uses += vorig.Uses vorig.Link = v } else if v := s.Parent.findUndeclared(vorig.Data); v != nil { // check if variable is already used before in the current or lower scopes v.Uses += vorig.Uses vorig.Link = v } else { // add variable to the context list and to the scope's undeclared vorig.Decl = NoDecl s.Parent.Undeclared = append(s.Parent.Undeclared, vorig) } } s.Declared = s.Declared[:0] s.Undeclared = s.Undeclared[:0] } // Unscope moves all declared variables of the current scope to the parent scope. Undeclared variables are already in the parent scope. func (s *Scope) Unscope() { for _, vorig := range s.Declared { // no need to evaluate vorig.Link as vorig.Data stays the same, and Link is always nil in Declared // vorig.Uses will be atleast 1 s.Parent.Declared = append(s.Parent.Declared, vorig) } s.Declared = s.Declared[:0] s.Undeclared = s.Undeclared[:0] } //////////////////////////////////////////////////////////////// // INode is an interface for AST nodes type INode interface { String() string JS(io.Writer) } // IStmt is a dummy interface for statements. type IStmt interface { INode stmtNode() } // IBinding is a dummy interface for bindings. type IBinding interface { INode bindingNode() } // IExpr is a dummy interface for expressions. type IExpr interface { INode exprNode() } //////////////////////////////////////////////////////////////// // Comment block or line, usually a bang comment. type Comment struct { Value []byte } func (n Comment) String() string { return "Stmt(" + string(n.Value) + ")" } // JS writes JavaScript to writer. func (n Comment) JS(w io.Writer) { w.Write(n.Value) w.Write([]byte("\n")) } // BlockStmt is a block statement. type BlockStmt struct { List []IStmt Scope } func (n BlockStmt) String() string { s := "Stmt({" for _, item := range n.List { s += " " + item.String() } return s + " })" } // JS writes JavaScript to writer. func (n BlockStmt) JS(w io.Writer) { if len(n.List) == 0 { w.Write([]byte("{}")) return } w.Write([]byte("{")) wi := NewIndenter(w, 4) for _, item := range n.List { wi.Write([]byte("\n")) item.JS(wi) if _, ok := item.(*VarDecl); ok { w.Write([]byte(";")) } } w.Write([]byte("\n}")) } // EmptyStmt is an empty statement. type EmptyStmt struct{} func (n EmptyStmt) String() string { return "Stmt()" } // JS writes JavaScript to writer. func (n EmptyStmt) JS(w io.Writer) { w.Write([]byte(";")) } // ExprStmt is an expression statement. type ExprStmt struct { Value IExpr } func (n ExprStmt) String() string { val := n.Value.String() if val[0] == '(' && val[len(val)-1] == ')' { return "Stmt" + n.Value.String() } return "Stmt(" + n.Value.String() + ")" } // JS writes JavaScript to writer. func (n ExprStmt) JS(w io.Writer) { n.Value.JS(w) w.Write([]byte(";")) } // IfStmt is an if statement. type IfStmt struct { Cond IExpr Body IStmt Else IStmt // can be nil } func (n IfStmt) String() string { s := "Stmt(if " + n.Cond.String() + " " + n.Body.String() if n.Else != nil { s += " else " + n.Else.String() } return s + ")" } // JS writes JavaScript to writer. func (n IfStmt) JS(w io.Writer) { w.Write([]byte("if (")) n.Cond.JS(w) w.Write([]byte(")")) if _, ok := n.Body.(*EmptyStmt); !ok { w.Write([]byte(" ")) } n.Body.JS(w) if _, ok := n.Body.(*VarDecl); ok { w.Write([]byte(";")) } if n.Else != nil { w.Write([]byte(" else")) if _, ok := n.Else.(*EmptyStmt); !ok { w.Write([]byte(" ")) } n.Else.JS(w) if _, ok := n.Else.(*VarDecl); ok { w.Write([]byte(";")) } } } // DoWhileStmt is a do-while iteration statement. type DoWhileStmt struct { Cond IExpr Body IStmt } func (n DoWhileStmt) String() string { return "Stmt(do " + n.Body.String() + " while " + n.Cond.String() + ")" } // JS writes JavaScript to writer. func (n DoWhileStmt) JS(w io.Writer) { w.Write([]byte("do")) if _, ok := n.Body.(*EmptyStmt); !ok { w.Write([]byte(" ")) } n.Body.JS(w) if _, ok := n.Body.(*VarDecl); ok { w.Write([]byte(";")) } w.Write([]byte(" while (")) n.Cond.JS(w) w.Write([]byte(");")) } // WhileStmt is a while iteration statement. type WhileStmt struct { Cond IExpr Body IStmt } func (n WhileStmt) String() string { return "Stmt(while " + n.Cond.String() + " " + n.Body.String() + ")" } // JS writes JavaScript to writer. func (n WhileStmt) JS(w io.Writer) { w.Write([]byte("while (")) n.Cond.JS(w) w.Write([]byte(")")) if _, ok := n.Body.(*EmptyStmt); !ok { w.Write([]byte(" ")) } n.Body.JS(w) if _, ok := n.Body.(*VarDecl); ok { w.Write([]byte(";")) } } // ForStmt is a regular for iteration statement. type ForStmt struct { Init IExpr // can be nil Cond IExpr // can be nil Post IExpr // can be nil Body *BlockStmt } func (n ForStmt) String() string { s := "Stmt(for" if v, ok := n.Init.(*VarDecl); !ok && n.Init != nil || ok && len(v.List) != 0 { s += " " + n.Init.String() } s += " ;" if n.Cond != nil { s += " " + n.Cond.String() } s += " ;" if n.Post != nil { s += " " + n.Post.String() } return s + " " + n.Body.String() + ")" } // JS writes JavaScript to writer. func (n ForStmt) JS(w io.Writer) { w.Write([]byte("for (")) if v, ok := n.Init.(*VarDecl); !ok && n.Init != nil || ok && len(v.List) != 0 { n.Init.JS(w) } else { w.Write([]byte(" ")) } w.Write([]byte("; ")) if n.Cond != nil { n.Cond.JS(w) } w.Write([]byte("; ")) if n.Post != nil { n.Post.JS(w) } w.Write([]byte(") ")) n.Body.JS(w) } // ForInStmt is a for-in iteration statement. type ForInStmt struct { Init IExpr Value IExpr Body *BlockStmt } func (n ForInStmt) String() string { return "Stmt(for " + n.Init.String() + " in " + n.Value.String() + " " + n.Body.String() + ")" } // JS writes JavaScript to writer. func (n ForInStmt) JS(w io.Writer) { w.Write([]byte("for (")) n.Init.JS(w) w.Write([]byte(" in ")) n.Value.JS(w) w.Write([]byte(") ")) n.Body.JS(w) } // ForOfStmt is a for-of iteration statement. type ForOfStmt struct { Await bool Init IExpr Value IExpr Body *BlockStmt } func (n ForOfStmt) String() string { s := "Stmt(for" if n.Await { s += " await" } return s + " " + n.Init.String() + " of " + n.Value.String() + " " + n.Body.String() + ")" } // JS writes JavaScript to writer. func (n ForOfStmt) JS(w io.Writer) { w.Write([]byte("for")) if n.Await { w.Write([]byte(" await")) } w.Write([]byte(" (")) n.Init.JS(w) w.Write([]byte(" of ")) n.Value.JS(w) w.Write([]byte(") ")) n.Body.JS(w) } // CaseClause is a case clause or default clause for a switch statement. type CaseClause struct { TokenType Cond IExpr // can be nil List []IStmt } func (n CaseClause) String() string { s := " Clause(" + n.TokenType.String() if n.Cond != nil { s += " " + n.Cond.String() } for _, item := range n.List { s += " " + item.String() } return s + ")" } // JS writes JavaScript to writer. func (n CaseClause) JS(w io.Writer) { if n.Cond != nil { w.Write([]byte("case ")) n.Cond.JS(w) } else { w.Write([]byte("default")) } w.Write([]byte(":")) wi := NewIndenter(w, 4) for _, item := range n.List { wi.Write([]byte("\n")) item.JS(wi) if _, ok := item.(*VarDecl); ok { w.Write([]byte(";")) } } } // SwitchStmt is a switch statement. type SwitchStmt struct { Init IExpr List []CaseClause Scope } func (n SwitchStmt) String() string { s := "Stmt(switch " + n.Init.String() for _, clause := range n.List { s += clause.String() } return s + ")" } // JS writes JavaScript to writer. func (n SwitchStmt) JS(w io.Writer) { w.Write([]byte("switch (")) n.Init.JS(w) if len(n.List) == 0 { w.Write([]byte(") {}")) return } w.Write([]byte(") {")) for _, clause := range n.List { w.Write([]byte("\n")) clause.JS(w) } w.Write([]byte("\n}")) } // BranchStmt is a continue or break statement. type BranchStmt struct { Type TokenType Label []byte // can be nil } func (n BranchStmt) String() string { s := "Stmt(" + n.Type.String() if n.Label != nil { s += " " + string(n.Label) } return s + ")" } // JS writes JavaScript to writer. func (n BranchStmt) JS(w io.Writer) { w.Write(n.Type.Bytes()) if n.Label != nil { w.Write([]byte(" ")) w.Write(n.Label) } w.Write([]byte(";")) } // ReturnStmt is a return statement. type ReturnStmt struct { Value IExpr // can be nil } func (n ReturnStmt) String() string { s := "Stmt(return" if n.Value != nil { s += " " + n.Value.String() } return s + ")" } // JS writes JavaScript to writer. func (n ReturnStmt) JS(w io.Writer) { w.Write([]byte("return")) if n.Value != nil { w.Write([]byte(" ")) n.Value.JS(w) } w.Write([]byte(";")) } // WithStmt is a with statement. type WithStmt struct { Cond IExpr Body IStmt } func (n WithStmt) String() string { return "Stmt(with " + n.Cond.String() + " " + n.Body.String() + ")" } // JS writes JavaScript to writer. func (n WithStmt) JS(w io.Writer) { w.Write([]byte("with (")) n.Cond.JS(w) w.Write([]byte(")")) if _, ok := n.Body.(*EmptyStmt); !ok { w.Write([]byte(" ")) } n.Body.JS(w) if _, ok := n.Body.(*VarDecl); ok { w.Write([]byte(";")) } } // LabelledStmt is a labelled statement. type LabelledStmt struct { Label []byte Value IStmt } func (n LabelledStmt) String() string { return "Stmt(" + string(n.Label) + " : " + n.Value.String() + ")" } // JS writes JavaScript to writer. func (n LabelledStmt) JS(w io.Writer) { w.Write(n.Label) w.Write([]byte(":")) if _, ok := n.Value.(*EmptyStmt); !ok { w.Write([]byte(" ")) } n.Value.JS(w) if _, ok := n.Value.(*VarDecl); ok { w.Write([]byte(";")) } } // ThrowStmt is a throw statement. type ThrowStmt struct { Value IExpr } func (n ThrowStmt) String() string { return "Stmt(throw " + n.Value.String() + ")" } // JS writes JavaScript to writer. func (n ThrowStmt) JS(w io.Writer) { w.Write([]byte("throw ")) n.Value.JS(w) w.Write([]byte(";")) } // TryStmt is a try statement. type TryStmt struct { Body *BlockStmt Binding IBinding // can be nil Catch *BlockStmt // can be nil Finally *BlockStmt // can be nil } func (n TryStmt) String() string { s := "Stmt(try " + n.Body.String() if n.Catch != nil { s += " catch" if n.Binding != nil { s += " Binding(" + n.Binding.String() + ")" } s += " " + n.Catch.String() } if n.Finally != nil { s += " finally " + n.Finally.String() } return s + ")" } // JS writes JavaScript to writer. func (n TryStmt) JS(w io.Writer) { w.Write([]byte("try ")) n.Body.JS(w) if n.Catch != nil { w.Write([]byte(" catch")) if n.Binding != nil { w.Write([]byte("(")) n.Binding.JS(w) w.Write([]byte(")")) } w.Write([]byte(" ")) n.Catch.JS(w) } if n.Finally != nil { w.Write([]byte(" finally ")) n.Finally.JS(w) } } // DebuggerStmt is a debugger statement. type DebuggerStmt struct{} func (n DebuggerStmt) String() string { return "Stmt(debugger)" } // JS writes JavaScript to writer. func (n DebuggerStmt) JS(w io.Writer) { w.Write([]byte("debugger;")) } // Alias is a name space import or import/export specifier for import/export statements. type Alias struct { Name []byte // can be nil Binding []byte // can be nil } func (alias Alias) String() string { s := "" if alias.Name != nil { s += string(alias.Name) + " as " } return s + string(alias.Binding) } // JS writes JavaScript to writer. func (alias Alias) JS(w io.Writer) { if alias.Name != nil { w.Write(alias.Name) w.Write([]byte(" as ")) } w.Write(alias.Binding) } // ImportStmt is an import statement. type ImportStmt struct { List []Alias Default []byte // can be nil Module []byte } func (n ImportStmt) String() string { s := "Stmt(import" if n.Default != nil { s += " " + string(n.Default) if n.List != nil { s += " ," } } if len(n.List) == 1 && len(n.List[0].Name) == 1 && n.List[0].Name[0] == '*' { s += " " + n.List[0].String() } else if n.List != nil { s += " {" for i, item := range n.List { if i != 0 { s += " ," } if item.Binding != nil { s += " " + item.String() } } s += " }" } if n.Default != nil || n.List != nil { s += " from" } return s + " " + string(n.Module) + ")" } // JS writes JavaScript to writer. func (n ImportStmt) JS(w io.Writer) { w.Write([]byte("import")) if n.Default != nil { w.Write([]byte(" ")) w.Write(n.Default) if n.List != nil { w.Write([]byte(",")) } } if len(n.List) == 1 && len(n.List[0].Name) == 1 && n.List[0].Name[0] == '*' { w.Write([]byte(" ")) n.List[0].JS(w) } else if n.List != nil { if len(n.List) == 0 { w.Write([]byte(" {}")) } else { w.Write([]byte(" {")) for j, item := range n.List { if j != 0 { w.Write([]byte(",")) } if item.Binding != nil { w.Write([]byte(" ")) item.JS(w) } } w.Write([]byte(" }")) } } if n.Default != nil || n.List != nil { w.Write([]byte(" from")) } w.Write([]byte(" ")) w.Write(n.Module) w.Write([]byte(";")) } // ExportStmt is an export statement. type ExportStmt struct { List []Alias Module []byte // can be nil Default bool Decl IExpr } func (n ExportStmt) String() string { s := "Stmt(export" if n.Decl != nil { if n.Default { s += " default" } return s + " " + n.Decl.String() + ")" } else if len(n.List) == 1 && (len(n.List[0].Name) == 1 && n.List[0].Name[0] == '*' || n.List[0].Name == nil && len(n.List[0].Binding) == 1 && n.List[0].Binding[0] == '*') { s += " " + n.List[0].String() } else if 0 < len(n.List) { s += " {" for i, item := range n.List { if i != 0 { s += " ," } if item.Binding != nil { s += " " + item.String() } } s += " }" } if n.Module != nil { s += " from " + string(n.Module) } return s + ")" } // JS writes JavaScript to writer. func (n ExportStmt) JS(w io.Writer) { w.Write([]byte("export")) if n.Decl != nil { if n.Default { w.Write([]byte(" default")) } w.Write([]byte(" ")) n.Decl.JS(w) w.Write([]byte(";")) return } else if len(n.List) == 1 && (len(n.List[0].Name) == 1 && n.List[0].Name[0] == '*' || n.List[0].Name == nil && len(n.List[0].Binding) == 1 && n.List[0].Binding[0] == '*') { w.Write([]byte(" ")) n.List[0].JS(w) } else if len(n.List) == 0 { w.Write([]byte(" {}")) } else { w.Write([]byte(" {")) for j, item := range n.List { if j != 0 { w.Write([]byte(",")) } if item.Binding != nil { w.Write([]byte(" ")) item.JS(w) } } w.Write([]byte(" }")) } if n.Module != nil { w.Write([]byte(" from ")) w.Write(n.Module) } w.Write([]byte(";")) } // DirectivePrologueStmt is a string literal at the beginning of a function or module (usually "use strict"). type DirectivePrologueStmt struct { Value []byte } func (n DirectivePrologueStmt) String() string { return "Stmt(" + string(n.Value) + ")" } // JS writes JavaScript to writer. func (n DirectivePrologueStmt) JS(w io.Writer) { w.Write(n.Value) w.Write([]byte(";")) } func (n Comment) stmtNode() {} func (n BlockStmt) stmtNode() {} func (n EmptyStmt) stmtNode() {} func (n ExprStmt) stmtNode() {} func (n IfStmt) stmtNode() {} func (n DoWhileStmt) stmtNode() {} func (n WhileStmt) stmtNode() {} func (n ForStmt) stmtNode() {} func (n ForInStmt) stmtNode() {} func (n ForOfStmt) stmtNode() {} func (n SwitchStmt) stmtNode() {} func (n BranchStmt) stmtNode() {} func (n ReturnStmt) stmtNode() {} func (n WithStmt) stmtNode() {} func (n LabelledStmt) stmtNode() {} func (n ThrowStmt) stmtNode() {} func (n TryStmt) stmtNode() {} func (n DebuggerStmt) stmtNode() {} func (n ImportStmt) stmtNode() {} func (n ExportStmt) stmtNode() {} func (n DirectivePrologueStmt) stmtNode() {} //////////////////////////////////////////////////////////////// // PropertyName is a property name for binding properties, method names, and in object literals. type PropertyName struct { Literal LiteralExpr Computed IExpr // can be nil } // IsSet returns true is PropertyName is not nil. func (n PropertyName) IsSet() bool { return n.IsComputed() || n.Literal.TokenType != ErrorToken } // IsComputed returns true if PropertyName is computed. func (n PropertyName) IsComputed() bool { return n.Computed != nil } // IsIdent returns true if PropertyName equals the given identifier name. func (n PropertyName) IsIdent(data []byte) bool { return !n.IsComputed() && n.Literal.TokenType == IdentifierToken && bytes.Equal(data, n.Literal.Data) } func (n PropertyName) String() string { if n.Computed != nil { val := n.Computed.String() if val[0] == '(' { return "[" + val[1:len(val)-1] + "]" } return "[" + val + "]" } return string(n.Literal.Data) } // JS writes JavaScript to writer. func (n PropertyName) JS(w io.Writer) { if n.Computed != nil { w.Write([]byte("[")) n.Computed.JS(w) w.Write([]byte("]")) return } w.Write(n.Literal.Data) } // BindingArray is an array binding pattern. type BindingArray struct { List []BindingElement Rest IBinding // can be nil } func (n BindingArray) String() string { s := "[" for i, item := range n.List { if i != 0 { s += "," } s += " " + item.String() } if n.Rest != nil { if len(n.List) != 0 { s += "," } s += " ...Binding(" + n.Rest.String() + ")" } return s + " ]" } // JS writes JavaScript to writer. func (n BindingArray) JS(w io.Writer) { w.Write([]byte("[")) for j, item := range n.List { if j != 0 { w.Write([]byte(", ")) } item.JS(w) } if n.Rest != nil { if len(n.List) != 0 { w.Write([]byte(", ")) } w.Write([]byte("...")) n.Rest.JS(w) } w.Write([]byte("]")) } // BindingObjectItem is a binding property. type BindingObjectItem struct { Key *PropertyName // can be nil Value BindingElement } func (n BindingObjectItem) String() string { s := "" if n.Key != nil { if v, ok := n.Value.Binding.(*Var); !ok || !n.Key.IsIdent(v.Data) { s += " " + n.Key.String() + ":" } } return s + " " + n.Value.String() } // JS writes JavaScript to writer. func (n BindingObjectItem) JS(w io.Writer) { if n.Key != nil { if v, ok := n.Value.Binding.(*Var); !ok || !n.Key.IsIdent(v.Data) { n.Key.JS(w) w.Write([]byte(": ")) } } n.Value.JS(w) } // BindingObject is an object binding pattern. type BindingObject struct { List []BindingObjectItem Rest *Var // can be nil } func (n BindingObject) String() string { s := "{" for i, item := range n.List { if i != 0 { s += "," } s += item.String() } if n.Rest != nil { if len(n.List) != 0 { s += "," } s += " ...Binding(" + string(n.Rest.Data) + ")" } return s + " }" } // JS writes JavaScript to writer. func (n BindingObject) JS(w io.Writer) { w.Write([]byte("{")) for j, item := range n.List { if j != 0 { w.Write([]byte(", ")) } item.JS(w) } if n.Rest != nil { if len(n.List) != 0 { w.Write([]byte(", ")) } w.Write([]byte("...")) w.Write(n.Rest.Data) } w.Write([]byte("}")) } // BindingElement is a binding element. type BindingElement struct { Binding IBinding // can be nil (in case of ellision) Default IExpr // can be nil } func (n BindingElement) String() string { if n.Binding == nil { return "Binding()" } s := "Binding(" + n.Binding.String() if n.Default != nil { s += " = " + n.Default.String() } return s + ")" } // JS writes JavaScript to writer. func (n BindingElement) JS(w io.Writer) { if n.Binding == nil { return } n.Binding.JS(w) if n.Default != nil { w.Write([]byte(" = ")) n.Default.JS(w) } } func (v *Var) bindingNode() {} func (n BindingArray) bindingNode() {} func (n BindingObject) bindingNode() {} //////////////////////////////////////////////////////////////// // VarDecl is a variable statement or lexical declaration. type VarDecl struct { TokenType List []BindingElement Scope *Scope InFor, InForInOf bool } func (n VarDecl) String() string { s := "Decl(" + n.TokenType.String() for _, item := range n.List { s += " " + item.String() } return s + ")" } // JS writes JavaScript to writer. func (n VarDecl) JS(w io.Writer) { w.Write(n.TokenType.Bytes()) for j, item := range n.List { if j != 0 { w.Write([]byte(",")) } w.Write([]byte(" ")) item.JS(w) } } // Params is a list of parameters for functions, methods, and arrow function. type Params struct { List []BindingElement Rest IBinding // can be nil } func (n Params) String() string { s := "Params(" for i, item := range n.List { if i != 0 { s += ", " } s += item.String() } if n.Rest != nil { if len(n.List) != 0 { s += ", " } s += "...Binding(" + n.Rest.String() + ")" } return s + ")" } // JS writes JavaScript to writer. func (n Params) JS(w io.Writer) { w.Write([]byte("(")) for j, item := range n.List { if j != 0 { w.Write([]byte(", ")) } item.JS(w) } if n.Rest != nil { if len(n.List) != 0 { w.Write([]byte(", ")) } w.Write([]byte("...")) n.Rest.JS(w) } w.Write([]byte(")")) } // FuncDecl is an (async) (generator) function declaration or expression. type FuncDecl struct { Async bool Generator bool Name *Var // can be nil Params Params Body BlockStmt } func (n FuncDecl) String() string { s := "Decl(" if n.Async { s += "async function" } else { s += "function" } if n.Generator { s += "*" } if n.Name != nil { s += " " + string(n.Name.Data) } return s + " " + n.Params.String() + " " + n.Body.String() + ")" } // JS writes JavaScript to writer. func (n FuncDecl) JS(w io.Writer) { if n.Async { w.Write([]byte("async function")) } else { w.Write([]byte("function")) } if n.Generator { w.Write([]byte("*")) } if n.Name != nil { w.Write([]byte(" ")) w.Write(n.Name.Data) } n.Params.JS(w) w.Write([]byte(" ")) n.Body.JS(w) } // MethodDecl is a method definition in a class declaration. type MethodDecl struct { Static bool Async bool Generator bool Get bool Set bool Name PropertyName Params Params Body BlockStmt } func (n MethodDecl) String() string { s := "" if n.Static { s += " static" } if n.Async { s += " async" } if n.Generator { s += " *" } if n.Get { s += " get" } if n.Set { s += " set" } s += " " + n.Name.String() + " " + n.Params.String() + " " + n.Body.String() return "Method(" + s[1:] + ")" } // JS writes JavaScript to writer. func (n MethodDecl) JS(w io.Writer) { writen := false if n.Static { w.Write([]byte("static")) writen = true } if n.Async { if writen { w.Write([]byte(" ")) } w.Write([]byte("async")) writen = true } if n.Generator { if writen { w.Write([]byte(" ")) } w.Write([]byte("*")) writen = true } if n.Get { if writen { w.Write([]byte(" ")) } w.Write([]byte("get")) writen = true } if n.Set { if writen { w.Write([]byte(" ")) } w.Write([]byte("set")) writen = true } if writen { w.Write([]byte(" ")) } n.Name.JS(w) w.Write([]byte(" ")) n.Params.JS(w) w.Write([]byte(" ")) n.Body.JS(w) } // Field is a field definition in a class declaration. type Field struct { Static bool Name PropertyName Init IExpr } func (n Field) String() string { s := "Field(" if n.Static { s += "static " } s += n.Name.String() if n.Init != nil { s += " = " + n.Init.String() } return s + ")" } // JS writes JavaScript to writer. func (n Field) JS(w io.Writer) { if n.Static { w.Write([]byte("static ")) } n.Name.JS(w) if n.Init != nil { w.Write([]byte(" = ")) n.Init.JS(w) } } // ClassElement is a class element that is either a static block, a field definition, or a class method type ClassElement struct { StaticBlock *BlockStmt // can be nil Method *MethodDecl // can be nil Field } func (n ClassElement) String() string { if n.StaticBlock != nil { return "Static(" + n.StaticBlock.String() + ")" } else if n.Method != nil { return n.Method.String() } return n.Field.String() } // JS writes JavaScript to writer. func (n ClassElement) JS(w io.Writer) { if n.StaticBlock != nil { w.Write([]byte("static ")) n.StaticBlock.JS(w) return } else if n.Method != nil { n.Method.JS(w) return } n.Field.JS(w) w.Write([]byte(";")) } // ClassDecl is a class declaration. type ClassDecl struct { Name *Var // can be nil Extends IExpr // can be nil List []ClassElement } func (n ClassDecl) String() string { s := "Decl(class" if n.Name != nil { s += " " + string(n.Name.Data) } if n.Extends != nil { s += " extends " + n.Extends.String() } for _, item := range n.List { s += " " + item.String() } return s + ")" } // JS writes JavaScript to writer. func (n ClassDecl) JS(w io.Writer) { w.Write([]byte("class")) if n.Name != nil { w.Write([]byte(" ")) w.Write(n.Name.Data) } if n.Extends != nil { w.Write([]byte(" extends ")) n.Extends.JS(w) } if len(n.List) == 0 { w.Write([]byte(" {}")) return } w.Write([]byte(" {")) wi := NewIndenter(w, 4) for _, item := range n.List { wi.Write([]byte("\n")) item.JS(wi) } w.Write([]byte("\n}")) } func (n VarDecl) stmtNode() {} func (n FuncDecl) stmtNode() {} func (n ClassDecl) stmtNode() {} func (n VarDecl) exprNode() {} // not a real IExpr, used for ForInit and ExportDecl func (n FuncDecl) exprNode() {} func (n ClassDecl) exprNode() {} func (n MethodDecl) exprNode() {} // not a real IExpr, used for ObjectExpression PropertyName //////////////////////////////////////////////////////////////// // LiteralExpr can be this, null, boolean, numeric, string, or regular expression literals. type LiteralExpr struct { TokenType Data []byte } func (n LiteralExpr) String() string { return string(n.Data) } // JS writes JavaScript to writer. func (n LiteralExpr) JS(w io.Writer) { w.Write(n.Data) } // JSON writes JSON to writer. func (n LiteralExpr) JSON(w io.Writer) error { if n.TokenType == TrueToken || n.TokenType == FalseToken || n.TokenType == NullToken || n.TokenType == DecimalToken { w.Write(n.Data) return nil } else if n.TokenType == StringToken { data := n.Data if n.Data[0] == '\'' { data = parse.Copy(data) data = bytes.ReplaceAll(data, []byte(`\'`), []byte(`'`)) data = bytes.ReplaceAll(data, []byte(`"`), []byte(`\"`)) data[0] = '"' data[len(data)-1] = '"' } w.Write(data) return nil } return ErrInvalidJSON } // Element is an array literal element. type Element struct { Value IExpr // can be nil Spread bool } func (n Element) String() string { s := "" if n.Value != nil { if n.Spread { s += "..." } s += n.Value.String() } return s } // JS writes JavaScript to writer. func (n Element) JS(w io.Writer) { if n.Value != nil { if n.Spread { w.Write([]byte("...")) } n.Value.JS(w) } } // ArrayExpr is an array literal. type ArrayExpr struct { List []Element } func (n ArrayExpr) String() string { s := "[" for i, item := range n.List { if i != 0 { s += ", " } if item.Value != nil { if item.Spread { s += "..." } s += item.Value.String() } } if 0 < len(n.List) && n.List[len(n.List)-1].Value == nil { s += "," } return s + "]" } // JS writes JavaScript to writer. func (n ArrayExpr) JS(w io.Writer) { w.Write([]byte("[")) for j, item := range n.List { if j != 0 { w.Write([]byte(", ")) } if item.Value != nil { if item.Spread { w.Write([]byte("...")) } item.Value.JS(w) } } if 0 < len(n.List) && n.List[len(n.List)-1].Value == nil { w.Write([]byte(",")) } w.Write([]byte("]")) } // JSON writes JSON to writer. func (n ArrayExpr) JSON(w io.Writer) error { w.Write([]byte("[")) for i, item := range n.List { if i != 0 { w.Write([]byte(", ")) } if item.Value == nil || item.Spread { return ErrInvalidJSON } if val, ok := item.Value.(JSONer); !ok { return ErrInvalidJSON } else if err := val.JSON(w); err != nil { return err } } w.Write([]byte("]")) return nil } // Property is a property definition in an object literal. type Property struct { // either Name or Spread are set. When Spread is set then Value is AssignmentExpression // if Init is set then Value is IdentifierReference, otherwise it can also be MethodDefinition Name *PropertyName // can be nil Spread bool Value IExpr Init IExpr // can be nil } func (n Property) String() string { s := "" if n.Name != nil { if v, ok := n.Value.(*Var); !ok || !n.Name.IsIdent(v.Data) { s += n.Name.String() + ": " } } else if n.Spread { s += "..." } s += n.Value.String() if n.Init != nil { s += " = " + n.Init.String() } return s } // JS writes JavaScript to writer. func (n Property) JS(w io.Writer) { if n.Name != nil { if v, ok := n.Value.(*Var); !ok || !n.Name.IsIdent(v.Data) { n.Name.JS(w) w.Write([]byte(": ")) } } else if n.Spread { w.Write([]byte("...")) } n.Value.JS(w) if n.Init != nil { w.Write([]byte(" = ")) n.Init.JS(w) } } // JSON writes JSON to writer. func (n Property) JSON(w io.Writer) error { if n.Name == nil || n.Name.Literal.TokenType != StringToken && n.Name.Literal.TokenType != IdentifierToken || n.Spread || n.Init != nil { return ErrInvalidJSON } else if n.Name.Literal.TokenType == IdentifierToken { w.Write([]byte(`"`)) w.Write(n.Name.Literal.Data) w.Write([]byte(`"`)) } else { _ = n.Name.Literal.JSON(w) } w.Write([]byte(": ")) if val, ok := n.Value.(JSONer); !ok { return ErrInvalidJSON } else if err := val.JSON(w); err != nil { return err } return nil } // ObjectExpr is an object literal. type ObjectExpr struct { List []Property } func (n ObjectExpr) String() string { s := "{" for i, item := range n.List { if i != 0 { s += ", " } s += item.String() } return s + "}" } // JS writes JavaScript to writer. func (n ObjectExpr) JS(w io.Writer) { w.Write([]byte("{")) for j, item := range n.List { if j != 0 { w.Write([]byte(", ")) } item.JS(w) } w.Write([]byte("}")) } // JSON writes JSON to writer. func (n ObjectExpr) JSON(w io.Writer) error { w.Write([]byte("{")) for i, item := range n.List { if i != 0 { w.Write([]byte(", ")) } if err := item.JSON(w); err != nil { return err } } w.Write([]byte("}")) return nil } // TemplatePart is a template head or middle. type TemplatePart struct { Value []byte Expr IExpr } func (n TemplatePart) String() string { return string(n.Value) + n.Expr.String() } // JS writes JavaScript to writer. func (n TemplatePart) JS(w io.Writer) { w.Write(n.Value) n.Expr.JS(w) } // TemplateExpr is a template literal or member/call expression, super property, or optional chain with template literal. type TemplateExpr struct { Tag IExpr // can be nil List []TemplatePart Tail []byte Prec OpPrec Optional bool } func (n TemplateExpr) String() string { s := "" if n.Tag != nil { s += n.Tag.String() if n.Optional { s += "?." } } for _, item := range n.List { s += item.String() } return s + string(n.Tail) } // JS writes JavaScript to writer. func (n TemplateExpr) JS(w io.Writer) { if n.Tag != nil { n.Tag.JS(w) if n.Optional { w.Write([]byte("?.")) } } for _, item := range n.List { item.JS(w) } w.Write(n.Tail) } // GroupExpr is a parenthesized expression. type GroupExpr struct { X IExpr } func (n GroupExpr) String() string { return "(" + n.X.String() + ")" } // JS writes JavaScript to writer. func (n GroupExpr) JS(w io.Writer) { w.Write([]byte("(")) n.X.JS(w) w.Write([]byte(")")) } // IndexExpr is a member/call expression, super property, or optional chain with an index expression. type IndexExpr struct { X IExpr Y IExpr Prec OpPrec Optional bool } func (n IndexExpr) String() string { if n.Optional { return "(" + n.X.String() + "?.[" + n.Y.String() + "])" } return "(" + n.X.String() + "[" + n.Y.String() + "])" } // JS writes JavaScript to writer. func (n IndexExpr) JS(w io.Writer) { n.X.JS(w) if n.Optional { w.Write([]byte("?.[")) } else { w.Write([]byte("[")) } n.Y.JS(w) w.Write([]byte("]")) } // DotExpr is a member/call expression, super property, or optional chain with a dot expression. type DotExpr struct { X IExpr Y LiteralExpr Prec OpPrec Optional bool } func (n DotExpr) String() string { if n.Optional { return "(" + n.X.String() + "?." + n.Y.String() + ")" } return "(" + n.X.String() + "." + n.Y.String() + ")" } // JS writes JavaScript to writer. func (n DotExpr) JS(w io.Writer) { lit, ok := n.X.(*LiteralExpr) group := ok && !n.Optional && lit.TokenType == DecimalToken if group { w.Write([]byte("(")) } n.X.JS(w) if n.Optional { w.Write([]byte("?.")) } else { if group { w.Write([]byte(")")) } w.Write([]byte(".")) } n.Y.JS(w) } // NewTargetExpr is a new target meta property. type NewTargetExpr struct{} func (n NewTargetExpr) String() string { return "(new.target)" } // JS writes JavaScript to writer. func (n NewTargetExpr) JS(w io.Writer) { w.Write([]byte("new.target")) } // ImportMetaExpr is a import meta meta property. type ImportMetaExpr struct{} func (n ImportMetaExpr) String() string { return "(import.meta)" } // JS writes JavaScript to writer. func (n ImportMetaExpr) JS(w io.Writer) { w.Write([]byte("import.meta")) } type Arg struct { Value IExpr Rest bool } func (n Arg) String() string { s := "" if n.Rest { s += "..." } return s + n.Value.String() } // JS writes JavaScript to writer. func (n Arg) JS(w io.Writer) { if n.Rest { w.Write([]byte("...")) } n.Value.JS(w) } // Args is a list of arguments as used by new and call expressions. type Args struct { List []Arg } func (n Args) String() string { s := "(" for i, item := range n.List { if i != 0 { s += ", " } s += item.String() } return s + ")" } // JS writes JavaScript to writer. func (n Args) JS(w io.Writer) { for j, item := range n.List { if j != 0 { w.Write([]byte(", ")) } item.JS(w) } } // NewExpr is a new expression or new member expression. type NewExpr struct { X IExpr Args *Args // can be nil } func (n NewExpr) String() string { if n.Args != nil { return "(new " + n.X.String() + n.Args.String() + ")" } return "(new " + n.X.String() + ")" } // JS writes JavaScript to writer. func (n NewExpr) JS(w io.Writer) { w.Write([]byte("new ")) n.X.JS(w) if n.Args != nil { w.Write([]byte("(")) n.Args.JS(w) w.Write([]byte(")")) } else { w.Write([]byte("()")) } } // CallExpr is a call expression. type CallExpr struct { X IExpr Args Args Optional bool } func (n CallExpr) String() string { if n.Optional { return "(" + n.X.String() + "?." + n.Args.String() + ")" } return "(" + n.X.String() + n.Args.String() + ")" } // JS writes JavaScript to writer. func (n CallExpr) JS(w io.Writer) { n.X.JS(w) if n.Optional { w.Write([]byte("?.(")) } else { w.Write([]byte("(")) } n.Args.JS(w) w.Write([]byte(")")) } // UnaryExpr is an update or unary expression. type UnaryExpr struct { Op TokenType X IExpr } func (n UnaryExpr) String() string { if n.Op == PostIncrToken || n.Op == PostDecrToken { return "(" + n.X.String() + n.Op.String() + ")" } else if IsIdentifierName(n.Op) { return "(" + n.Op.String() + " " + n.X.String() + ")" } return "(" + n.Op.String() + n.X.String() + ")" } // JS writes JavaScript to writer. func (n UnaryExpr) JS(w io.Writer) { if n.Op == PostIncrToken || n.Op == PostDecrToken { n.X.JS(w) w.Write(n.Op.Bytes()) return } else if unary, ok := n.X.(*UnaryExpr); ok && (n.Op == PosToken && (unary.Op == PreIncrToken || unary.Op == PosToken) || n.Op == NegToken && (unary.Op == PreDecrToken || unary.Op == NegToken)) || IsIdentifierName(n.Op) { w.Write(n.Op.Bytes()) w.Write([]byte(" ")) n.X.JS(w) return } w.Write(n.Op.Bytes()) n.X.JS(w) } // JSON writes JSON to writer. func (n UnaryExpr) JSON(w io.Writer) error { if lit, ok := n.X.(*LiteralExpr); ok && n.Op == NegToken && lit.TokenType == DecimalToken { w.Write([]byte("-")) w.Write(lit.Data) return nil } return ErrInvalidJSON } // BinaryExpr is a binary expression. type BinaryExpr struct { Op TokenType X, Y IExpr } func (n BinaryExpr) String() string { if IsIdentifierName(n.Op) { return "(" + n.X.String() + " " + n.Op.String() + " " + n.Y.String() + ")" } return "(" + n.X.String() + n.Op.String() + n.Y.String() + ")" } // JS writes JavaScript to writer. func (n BinaryExpr) JS(w io.Writer) { n.X.JS(w) w.Write([]byte(" ")) w.Write(n.Op.Bytes()) w.Write([]byte(" ")) n.Y.JS(w) } // CondExpr is a conditional expression. type CondExpr struct { Cond, X, Y IExpr } func (n CondExpr) String() string { return "(" + n.Cond.String() + " ? " + n.X.String() + " : " + n.Y.String() + ")" } // JS writes JavaScript to writer. func (n CondExpr) JS(w io.Writer) { n.Cond.JS(w) w.Write([]byte(" ? ")) n.X.JS(w) w.Write([]byte(" : ")) n.Y.JS(w) } // YieldExpr is a yield expression. type YieldExpr struct { Generator bool X IExpr // can be nil } func (n YieldExpr) String() string { if n.X == nil { return "(yield)" } s := "(yield" if n.Generator { s += "*" } return s + " " + n.X.String() + ")" } // JS writes JavaScript to writer. func (n YieldExpr) JS(w io.Writer) { w.Write([]byte("yield")) if n.X == nil { return } if n.Generator { w.Write([]byte("*")) } w.Write([]byte(" ")) n.X.JS(w) } // ArrowFunc is an (async) arrow function. type ArrowFunc struct { Async bool Params Params Body BlockStmt } func (n ArrowFunc) String() string { s := "(" if n.Async { s += "async " } return s + n.Params.String() + " => " + n.Body.String() + ")" } // JS writes JavaScript to writer. func (n ArrowFunc) JS(w io.Writer) { if n.Async { w.Write([]byte("async ")) } n.Params.JS(w) w.Write([]byte(" => ")) n.Body.JS(w) } // CommaExpr is a series of comma expressions. type CommaExpr struct { List []IExpr } func (n CommaExpr) String() string { s := "(" for i, item := range n.List { if i != 0 { s += "," } s += item.String() } return s + ")" } // JS writes JavaScript to writer. func (n CommaExpr) JS(w io.Writer) { for j, item := range n.List { if j != 0 { w.Write([]byte(",")) } item.JS(w) } } func (v *Var) exprNode() {} func (n LiteralExpr) exprNode() {} func (n ArrayExpr) exprNode() {} func (n ObjectExpr) exprNode() {} func (n TemplateExpr) exprNode() {} func (n GroupExpr) exprNode() {} func (n DotExpr) exprNode() {} func (n IndexExpr) exprNode() {} func (n NewTargetExpr) exprNode() {} func (n ImportMetaExpr) exprNode() {} func (n NewExpr) exprNode() {} func (n CallExpr) exprNode() {} func (n UnaryExpr) exprNode() {} func (n BinaryExpr) exprNode() {} func (n CondExpr) exprNode() {} func (n YieldExpr) exprNode() {} func (n ArrowFunc) exprNode() {} func (n CommaExpr) exprNode() {}