988 lines
34 KiB
Go
988 lines
34 KiB
Go
package printer
|
|
|
|
import (
|
|
"maps"
|
|
"slices"
|
|
"sync"
|
|
"sync/atomic"
|
|
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/collections"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
|
|
)
|
|
|
|
// Stores side-table information used during transformation that can be read by the printer to customize emit
|
|
//
|
|
// NOTE: EmitContext is not guaranteed to be thread-safe.
|
|
type EmitContext struct {
|
|
Factory *NodeFactory // Required. The NodeFactory to use to create new nodes
|
|
autoGenerate map[*ast.MemberName]*AutoGenerateInfo
|
|
textSource map[*ast.StringLiteralNode]*ast.Node
|
|
original map[*ast.Node]*ast.Node
|
|
emitNodes core.LinkStore[*ast.Node, emitNode]
|
|
assignedName map[*ast.Node]*ast.Expression
|
|
classThis map[*ast.Node]*ast.IdentifierNode
|
|
varScopeStack core.Stack[*varScope]
|
|
letScopeStack core.Stack[*varScope]
|
|
emitHelpers collections.OrderedSet[*EmitHelper]
|
|
}
|
|
|
|
type environmentFlags int
|
|
|
|
const (
|
|
environmentFlagsNone environmentFlags = 0
|
|
environmentFlagsInParameters environmentFlags = 1 << 0 // currently visiting a parameter list
|
|
environmentFlagsVariablesHoistedInParameters environmentFlags = 1 << 1 // a temp variable was hoisted while visiting a parameter list
|
|
)
|
|
|
|
type varScope struct {
|
|
variables []*ast.VariableDeclarationNode
|
|
functions []*ast.FunctionDeclarationNode
|
|
flags environmentFlags
|
|
initializationStatements []*ast.Node
|
|
}
|
|
|
|
func NewEmitContext() *EmitContext {
|
|
c := &EmitContext{}
|
|
c.Factory = NewNodeFactory(c)
|
|
return c
|
|
}
|
|
|
|
var emitContextPool = sync.Pool{
|
|
New: func() any {
|
|
return NewEmitContext()
|
|
},
|
|
}
|
|
|
|
func GetEmitContext() (*EmitContext, func()) {
|
|
c := emitContextPool.Get().(*EmitContext)
|
|
return c, func() {
|
|
c.Reset()
|
|
emitContextPool.Put(c)
|
|
}
|
|
}
|
|
|
|
func (c *EmitContext) Reset() {
|
|
*c = EmitContext{
|
|
Factory: c.Factory,
|
|
}
|
|
}
|
|
|
|
func (c *EmitContext) onCreate(node *ast.Node) {
|
|
node.Flags |= ast.NodeFlagsSynthesized
|
|
}
|
|
|
|
func (c *EmitContext) onUpdate(updated *ast.Node, original *ast.Node) {
|
|
c.SetOriginal(updated, original)
|
|
}
|
|
|
|
func (c *EmitContext) onClone(updated *ast.Node, original *ast.Node) {
|
|
if ast.IsIdentifier(updated) || ast.IsPrivateIdentifier(updated) {
|
|
if autoGenerate := c.autoGenerate[original]; autoGenerate != nil {
|
|
autoGenerateCopy := *autoGenerate
|
|
c.autoGenerate[updated] = &autoGenerateCopy
|
|
}
|
|
}
|
|
}
|
|
|
|
// Creates a new NodeVisitor attached to this EmitContext
|
|
func (c *EmitContext) NewNodeVisitor(visit func(node *ast.Node) *ast.Node) *ast.NodeVisitor {
|
|
return ast.NewNodeVisitor(visit, c.Factory.AsNodeFactory(), ast.NodeVisitorHooks{
|
|
VisitParameters: c.VisitParameters,
|
|
VisitFunctionBody: c.VisitFunctionBody,
|
|
VisitIterationBody: c.VisitIterationBody,
|
|
VisitTopLevelStatements: c.VisitVariableEnvironment,
|
|
VisitEmbeddedStatement: c.VisitEmbeddedStatement,
|
|
})
|
|
}
|
|
|
|
//
|
|
// Environment tracking
|
|
//
|
|
|
|
// Starts a new VariableEnvironment used to track hoisted `var` statements and function declarations.
|
|
//
|
|
// see: https://tc39.es/ecma262/#table-additional-state-components-for-ecmascript-code-execution-contexts
|
|
//
|
|
// NOTE: This is the equivalent of `transformContext.startLexicalEnvironment` in Strada.
|
|
func (c *EmitContext) StartVariableEnvironment() {
|
|
c.varScopeStack.Push(&varScope{})
|
|
c.StartLexicalEnvironment()
|
|
}
|
|
|
|
// Ends the current VariableEnvironment, returning a list of statements that should be emitted at the start of the current scope.
|
|
//
|
|
// NOTE: This is the equivalent of `transformContext.endLexicalEnvironment` in Strada.
|
|
func (c *EmitContext) EndVariableEnvironment() []*ast.Statement {
|
|
scope := c.varScopeStack.Pop()
|
|
var statements []*ast.Statement
|
|
if len(scope.functions) > 0 {
|
|
statements = slices.Clone(scope.functions)
|
|
}
|
|
if len(scope.variables) > 0 {
|
|
varDeclList := c.Factory.NewVariableDeclarationList(ast.NodeFlagsNone, c.Factory.NewNodeList(scope.variables))
|
|
varStatement := c.Factory.NewVariableStatement(nil /*modifiers*/, varDeclList)
|
|
c.SetEmitFlags(varStatement, EFCustomPrologue)
|
|
statements = append(statements, varStatement)
|
|
}
|
|
if len(scope.initializationStatements) > 0 {
|
|
statements = append(statements, scope.initializationStatements...)
|
|
}
|
|
return append(statements, c.EndLexicalEnvironment()...)
|
|
}
|
|
|
|
// Invokes c.EndVariableEnvironment() and merges the results into `statements`
|
|
func (c *EmitContext) EndAndMergeVariableEnvironmentList(statements *ast.StatementList) *ast.StatementList {
|
|
var nodes []*ast.Statement
|
|
if statements != nil {
|
|
nodes = statements.Nodes
|
|
}
|
|
|
|
if result, changed := c.endAndMergeVariableEnvironment(nodes); changed {
|
|
list := c.Factory.NewNodeList(result)
|
|
list.Loc = statements.Loc
|
|
return list
|
|
}
|
|
|
|
return statements
|
|
}
|
|
|
|
// Invokes c.EndVariableEnvironment() and merges the results into `statements`
|
|
func (c *EmitContext) EndAndMergeVariableEnvironment(statements []*ast.Statement) []*ast.Statement {
|
|
result, _ := c.endAndMergeVariableEnvironment(statements)
|
|
return result
|
|
}
|
|
|
|
func (c *EmitContext) endAndMergeVariableEnvironment(statements []*ast.Statement) ([]*ast.Statement, bool) {
|
|
return c.mergeEnvironment(statements, c.EndVariableEnvironment())
|
|
}
|
|
|
|
// Adds a `var` declaration to the current VariableEnvironment
|
|
//
|
|
// NOTE: This is the equivalent of `transformContext.hoistVariableDeclaration` in Strada.
|
|
func (c *EmitContext) AddVariableDeclaration(name *ast.IdentifierNode) {
|
|
varDecl := c.Factory.NewVariableDeclaration(name, nil /*exclamationToken*/, nil /*typeNode*/, nil /*initializer*/)
|
|
c.SetEmitFlags(varDecl, EFNoNestedSourceMaps)
|
|
scope := c.varScopeStack.Peek()
|
|
scope.variables = append(scope.variables, varDecl)
|
|
if scope.flags&environmentFlagsInParameters != 0 {
|
|
scope.flags |= environmentFlagsVariablesHoistedInParameters
|
|
}
|
|
}
|
|
|
|
// Adds a hoisted function declaration to the current VariableEnvironment
|
|
//
|
|
// NOTE: This is the equivalent of `transformContext.hoistFunctionDeclaration` in Strada.
|
|
func (c *EmitContext) AddHoistedFunctionDeclaration(node *ast.FunctionDeclarationNode) {
|
|
c.SetEmitFlags(node, EFCustomPrologue)
|
|
scope := c.varScopeStack.Peek()
|
|
scope.functions = append(scope.functions, node)
|
|
}
|
|
|
|
// Starts a new LexicalEnvironment used to track block-scoped `let`, `const`, and `using` declarations.
|
|
//
|
|
// see: https://tc39.es/ecma262/#table-additional-state-components-for-ecmascript-code-execution-contexts
|
|
//
|
|
// NOTE: This is the equivalent of `transformContext.startBlockScope` in Strada.
|
|
// NOTE: This is *not* the same as `startLexicalEnvironment` in Strada as that method is incorrectly named.
|
|
func (c *EmitContext) StartLexicalEnvironment() {
|
|
c.letScopeStack.Push(&varScope{})
|
|
}
|
|
|
|
// Ends the current EndLexicalEnvironment, returning a list of statements that should be emitted at the start of the current scope.
|
|
//
|
|
// NOTE: This is the equivalent of `transformContext.endLexicalEnvironment` in Strada.
|
|
// NOTE: This is *not* the same as `endLexicalEnvironment` in Strada as that method is incorrectly named.
|
|
func (c *EmitContext) EndLexicalEnvironment() []*ast.Statement {
|
|
scope := c.letScopeStack.Pop()
|
|
var statements []*ast.Statement
|
|
if len(scope.variables) > 0 {
|
|
varDeclList := c.Factory.NewVariableDeclarationList(ast.NodeFlagsLet, c.Factory.NewNodeList(scope.variables))
|
|
varStatement := c.Factory.NewVariableStatement(nil /*modifiers*/, varDeclList)
|
|
c.SetEmitFlags(varStatement, EFCustomPrologue)
|
|
statements = append(statements, varStatement)
|
|
}
|
|
return statements
|
|
}
|
|
|
|
// Invokes c.EndLexicalEnvironment() and merges the results into `statements`
|
|
func (c *EmitContext) EndAndMergeLexicalEnvironmentList(statements *ast.StatementList) *ast.StatementList {
|
|
var nodes []*ast.Statement
|
|
if statements != nil {
|
|
nodes = statements.Nodes
|
|
}
|
|
|
|
if result, changed := c.endAndMergeLexicalEnvironment(nodes); changed {
|
|
list := c.Factory.NewNodeList(result)
|
|
list.Loc = statements.Loc
|
|
return list
|
|
}
|
|
|
|
return statements
|
|
}
|
|
|
|
// Invokes c.EndLexicalEnvironment() and merges the results into `statements`
|
|
func (c *EmitContext) EndAndMergeLexicalEnvironment(statements []*ast.Statement) []*ast.Statement {
|
|
result, _ := c.endAndMergeLexicalEnvironment(statements)
|
|
return result
|
|
}
|
|
|
|
// Invokes c.EndLexicalEnvironment() and merges the results into `statements`
|
|
func (c *EmitContext) endAndMergeLexicalEnvironment(statements []*ast.Statement) ([]*ast.Statement, bool) {
|
|
return c.mergeEnvironment(statements, c.EndLexicalEnvironment())
|
|
}
|
|
|
|
// Adds a `let` declaration to the current LexicalEnvironment.
|
|
func (c *EmitContext) AddLexicalDeclaration(name *ast.IdentifierNode) {
|
|
varDecl := c.Factory.NewVariableDeclaration(name, nil /*exclamationToken*/, nil /*typeNode*/, nil /*initializer*/)
|
|
c.SetEmitFlags(varDecl, EFNoNestedSourceMaps)
|
|
scope := c.letScopeStack.Peek()
|
|
scope.variables = append(scope.variables, varDecl)
|
|
}
|
|
|
|
// Merges declarations produced by c.EndVariableEnvironment() or c.EndLexicalEnvironment() into a statement list
|
|
func (c *EmitContext) MergeEnvironmentList(statements *ast.StatementList, declarations []*ast.Statement) *ast.StatementList {
|
|
if result, changed := c.mergeEnvironment(statements.Nodes, declarations); changed {
|
|
list := c.Factory.NewNodeList(result)
|
|
list.Loc = statements.Loc
|
|
return list
|
|
}
|
|
return statements
|
|
}
|
|
|
|
// Merges declarations produced by c.EndVariableEnvironment() or c.EndLexicalEnvironment() into a slice of statements
|
|
func (c *EmitContext) MergeEnvironment(statements []*ast.Statement, declarations []*ast.Statement) []*ast.Statement {
|
|
result, _ := c.mergeEnvironment(statements, declarations)
|
|
return result
|
|
}
|
|
|
|
func (c *EmitContext) mergeEnvironment(statements []*ast.Statement, declarations []*ast.Statement) ([]*ast.Statement, bool) {
|
|
if len(declarations) == 0 {
|
|
return statements, false
|
|
}
|
|
|
|
// When we merge new lexical statements into an existing statement list, we merge them in the following manner:
|
|
//
|
|
// Given:
|
|
//
|
|
// | Left | Right |
|
|
// |------------------------------------|-------------------------------------|
|
|
// | [standard prologues (left)] | [standard prologues (right)] |
|
|
// | [hoisted functions (left)] | [hoisted functions (right)] |
|
|
// | [hoisted variables (left)] | [hoisted variables (right)] |
|
|
// | [lexical init statements (left)] | [lexical init statements (right)] |
|
|
// | [other statements (left)] | |
|
|
//
|
|
// The resulting statement list will be:
|
|
//
|
|
// | Result |
|
|
// |-------------------------------------|
|
|
// | [standard prologues (right)] |
|
|
// | [standard prologues (left)] |
|
|
// | [hoisted functions (right)] |
|
|
// | [hoisted functions (left)] |
|
|
// | [hoisted variables (right)] |
|
|
// | [hoisted variables (left)] |
|
|
// | [lexical init statements (right)] |
|
|
// | [lexical init statements (left)] |
|
|
// | [other statements (left)] |
|
|
//
|
|
// NOTE: It is expected that new lexical init statements must be evaluated before existing lexical init statements,
|
|
// as the prior transformation may depend on the evaluation of the lexical init statements to be in the correct state.
|
|
|
|
changed := false
|
|
|
|
// find standard prologues on left in the following order: standard directives, hoisted functions, hoisted variables, other custom
|
|
leftStandardPrologueEnd := findSpanEnd(statements, ast.IsPrologueDirective, 0)
|
|
leftHoistedFunctionsEnd := findSpanEndWithEmitContext(c, statements, (*EmitContext).isHoistedFunction, leftStandardPrologueEnd)
|
|
leftHoistedVariablesEnd := findSpanEndWithEmitContext(c, statements, (*EmitContext).isHoistedVariableStatement, leftHoistedFunctionsEnd)
|
|
|
|
// find standard prologues on right in the following order: standard directives, hoisted functions, hoisted variables, other custom
|
|
rightStandardPrologueEnd := findSpanEnd(declarations, ast.IsPrologueDirective, 0)
|
|
rightHoistedFunctionsEnd := findSpanEndWithEmitContext(c, declarations, (*EmitContext).isHoistedFunction, rightStandardPrologueEnd)
|
|
rightHoistedVariablesEnd := findSpanEndWithEmitContext(c, declarations, (*EmitContext).isHoistedVariableStatement, rightHoistedFunctionsEnd)
|
|
rightCustomPrologueEnd := findSpanEndWithEmitContext(c, declarations, (*EmitContext).isCustomPrologue, rightHoistedVariablesEnd)
|
|
if rightCustomPrologueEnd != len(declarations) {
|
|
panic("Expected declarations to be valid standard or custom prologues")
|
|
}
|
|
|
|
left := statements
|
|
|
|
// splice other custom prologues from right into left
|
|
if rightCustomPrologueEnd > rightHoistedVariablesEnd {
|
|
left = core.Splice(left, leftHoistedVariablesEnd, 0, declarations[rightHoistedVariablesEnd:rightCustomPrologueEnd]...)
|
|
changed = true
|
|
}
|
|
|
|
// splice hoisted variables from right into left
|
|
if rightHoistedVariablesEnd > rightHoistedFunctionsEnd {
|
|
left = core.Splice(left, leftHoistedFunctionsEnd, 0, declarations[rightHoistedFunctionsEnd:rightHoistedVariablesEnd]...)
|
|
changed = true
|
|
}
|
|
|
|
// splice hoisted functions from right into left
|
|
if rightHoistedFunctionsEnd > rightStandardPrologueEnd {
|
|
left = core.Splice(left, leftStandardPrologueEnd, 0, declarations[rightStandardPrologueEnd:rightHoistedFunctionsEnd]...)
|
|
changed = true
|
|
}
|
|
|
|
// splice standard prologues from right into left (that are not already in left)
|
|
if rightStandardPrologueEnd > 0 {
|
|
if leftStandardPrologueEnd == 0 {
|
|
left = core.Splice(left, 0, 0, declarations[:rightStandardPrologueEnd]...)
|
|
changed = true
|
|
} else {
|
|
var leftPrologues collections.Set[string]
|
|
for i := range leftStandardPrologueEnd {
|
|
leftPrologue := statements[i]
|
|
leftPrologues.Add(leftPrologue.Expression().Text())
|
|
}
|
|
for i := rightStandardPrologueEnd - 1; i >= 0; i-- {
|
|
rightPrologue := declarations[i]
|
|
if !leftPrologues.Has(rightPrologue.Expression().Text()) {
|
|
left = core.Concatenate([]*ast.Statement{rightPrologue}, left)
|
|
changed = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return left, changed
|
|
}
|
|
|
|
func (c *EmitContext) isCustomPrologue(node *ast.Statement) bool {
|
|
return c.EmitFlags(node)&EFCustomPrologue != 0
|
|
}
|
|
|
|
func (c *EmitContext) isHoistedFunction(node *ast.Statement) bool {
|
|
return c.isCustomPrologue(node) && ast.IsFunctionDeclaration(node)
|
|
}
|
|
|
|
func isHoistedVariable(node *ast.VariableDeclarationNode) bool {
|
|
return ast.IsIdentifier(node.Name()) && node.Initializer() == nil
|
|
}
|
|
|
|
func (c *EmitContext) isHoistedVariableStatement(node *ast.Statement) bool {
|
|
return c.isCustomPrologue(node) &&
|
|
ast.IsVariableStatement(node) &&
|
|
core.Every(node.AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes, isHoistedVariable)
|
|
}
|
|
|
|
//
|
|
// Name Generation
|
|
//
|
|
|
|
// Gets whether a given name has an associated AutoGenerateInfo entry.
|
|
func (c *EmitContext) HasAutoGenerateInfo(node *ast.MemberName) bool {
|
|
if node != nil {
|
|
_, ok := c.autoGenerate[node]
|
|
return ok
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Gets the associated AutoGenerateInfo entry for a given name.
|
|
func (c *EmitContext) GetAutoGenerateInfo(name *ast.MemberName) *AutoGenerateInfo {
|
|
if name == nil {
|
|
return nil
|
|
}
|
|
return c.autoGenerate[name]
|
|
}
|
|
|
|
// Walks the associated AutoGenerateInfo entries of a name to find the root Nopde from which the name should be generated.
|
|
func (c *EmitContext) GetNodeForGeneratedName(name *ast.MemberName) *ast.Node {
|
|
if autoGenerate := c.autoGenerate[name]; autoGenerate != nil && autoGenerate.Flags.IsNode() {
|
|
return c.getNodeForGeneratedNameWorker(autoGenerate.Node, autoGenerate.Id)
|
|
}
|
|
return name
|
|
}
|
|
|
|
func (c *EmitContext) getNodeForGeneratedNameWorker(node *ast.Node, autoGenerateId AutoGenerateId) *ast.Node {
|
|
original := c.Original(node)
|
|
for original != nil {
|
|
node = original
|
|
if ast.IsMemberName(node) {
|
|
// if "node" is a different generated name (having a different "autoGenerateId"), use it and stop traversing.
|
|
autoGenerate := c.autoGenerate[node]
|
|
if autoGenerate == nil || autoGenerate.Flags.IsNode() && autoGenerate.Id != autoGenerateId {
|
|
break
|
|
}
|
|
if autoGenerate.Flags.IsNode() {
|
|
original = autoGenerate.Node
|
|
continue
|
|
}
|
|
}
|
|
original = c.Original(node)
|
|
}
|
|
return node
|
|
}
|
|
|
|
type AutoGenerateOptions struct {
|
|
Flags GeneratedIdentifierFlags
|
|
Prefix string
|
|
Suffix string
|
|
}
|
|
|
|
var nextAutoGenerateId atomic.Uint32
|
|
|
|
type AutoGenerateId uint32
|
|
|
|
type AutoGenerateInfo struct {
|
|
Flags GeneratedIdentifierFlags // Specifies whether to auto-generate the text for an identifier.
|
|
Id AutoGenerateId // Ensures unique generated identifiers get unique names, but clones get the same name.
|
|
Prefix string // Optional prefix to apply to the start of the generated name
|
|
Suffix string // Optional suffix to apply to the end of the generated name
|
|
Node *ast.Node // For a GeneratedIdentifierFlagsNode, the node from which to generate an identifier
|
|
}
|
|
|
|
//
|
|
// Original Node Tracking
|
|
//
|
|
|
|
// Sets the original node for a given node.
|
|
//
|
|
// NOTE: This is the equivalent to `setOriginalNode` in Strada.
|
|
func (c *EmitContext) SetOriginal(node *ast.Node, original *ast.Node) {
|
|
c.SetOriginalEx(node, original, false)
|
|
}
|
|
|
|
func (c *EmitContext) SetOriginalEx(node *ast.Node, original *ast.Node, allowOverwrite bool) {
|
|
if original == nil {
|
|
panic("Original cannot be nil.")
|
|
}
|
|
|
|
if c.original == nil {
|
|
c.original = make(map[*ast.Node]*ast.Node)
|
|
}
|
|
|
|
existing, ok := c.original[node]
|
|
if !ok {
|
|
c.original[node] = original
|
|
if emitNode := c.emitNodes.TryGet(original); emitNode != nil {
|
|
c.emitNodes.Get(node).copyFrom(emitNode)
|
|
}
|
|
} else if !allowOverwrite && existing != original {
|
|
panic("Original node already set.")
|
|
} else if allowOverwrite {
|
|
c.original[node] = original
|
|
}
|
|
}
|
|
|
|
// Gets the original node for a given node.
|
|
//
|
|
// NOTE: This is the equivalent to reading `node.original` in Strada.
|
|
func (c *EmitContext) Original(node *ast.Node) *ast.Node {
|
|
return c.original[node]
|
|
}
|
|
|
|
// Gets the most original node associated with this node by walking Original pointers.
|
|
//
|
|
// NOTE: This method is analogous to `getOriginalNode` in the old compiler, but the name has changed to avoid accidental
|
|
// conflation with `SetOriginal`/`Original`
|
|
func (c *EmitContext) MostOriginal(node *ast.Node) *ast.Node {
|
|
if node != nil {
|
|
original := c.Original(node)
|
|
for original != nil {
|
|
node = original
|
|
original = c.Original(node)
|
|
}
|
|
}
|
|
return node
|
|
}
|
|
|
|
// Gets the original parse tree node for a given node.
|
|
//
|
|
// NOTE: This is the equivalent to `getParseTreeNode` in Strada.
|
|
func (c *EmitContext) ParseNode(node *ast.Node) *ast.Node {
|
|
node = c.MostOriginal(node)
|
|
if node != nil && ast.IsParseTreeNode(node) {
|
|
return node
|
|
}
|
|
return nil
|
|
}
|
|
|
|
//
|
|
// Emit-related Data
|
|
//
|
|
|
|
type emitNodeFlags uint32
|
|
|
|
const (
|
|
hasCommentRange emitNodeFlags = 1 << iota
|
|
hasSourceMapRange
|
|
)
|
|
|
|
type SynthesizedComment struct {
|
|
Kind ast.Kind
|
|
Loc core.TextRange
|
|
HasLeadingNewLine bool
|
|
HasTrailingNewLine bool
|
|
Text string
|
|
}
|
|
|
|
type emitNode struct {
|
|
flags emitNodeFlags
|
|
emitFlags EmitFlags
|
|
commentRange core.TextRange
|
|
sourceMapRange core.TextRange
|
|
tokenSourceMapRanges map[ast.Kind]core.TextRange
|
|
helpers []*EmitHelper
|
|
externalHelpersModuleName *ast.IdentifierNode
|
|
leadingComments []SynthesizedComment
|
|
trailingComments []SynthesizedComment
|
|
}
|
|
|
|
// NOTE: This method is not guaranteed to be thread-safe
|
|
func (e *emitNode) copyFrom(source *emitNode) {
|
|
e.flags = source.flags
|
|
e.emitFlags = source.emitFlags
|
|
e.commentRange = source.commentRange
|
|
e.sourceMapRange = source.sourceMapRange
|
|
e.tokenSourceMapRanges = maps.Clone(source.tokenSourceMapRanges)
|
|
e.helpers = slices.Clone(source.helpers)
|
|
e.externalHelpersModuleName = source.externalHelpersModuleName
|
|
}
|
|
|
|
func (c *EmitContext) EmitFlags(node *ast.Node) EmitFlags {
|
|
if emitNode := c.emitNodes.TryGet(node); emitNode != nil {
|
|
return emitNode.emitFlags
|
|
}
|
|
return EFNone
|
|
}
|
|
|
|
func (c *EmitContext) SetEmitFlags(node *ast.Node, flags EmitFlags) {
|
|
c.emitNodes.Get(node).emitFlags = flags
|
|
}
|
|
|
|
func (c *EmitContext) AddEmitFlags(node *ast.Node, flags EmitFlags) {
|
|
c.emitNodes.Get(node).emitFlags |= flags
|
|
}
|
|
|
|
// Gets the range to use for a node when emitting comments.
|
|
func (c *EmitContext) CommentRange(node *ast.Node) core.TextRange {
|
|
if emitNode := c.emitNodes.TryGet(node); emitNode != nil && emitNode.flags&hasCommentRange != 0 {
|
|
return emitNode.commentRange
|
|
}
|
|
return node.Loc
|
|
}
|
|
|
|
// Sets the range to use for a node when emitting comments.
|
|
func (c *EmitContext) SetCommentRange(node *ast.Node, loc core.TextRange) {
|
|
emitNode := c.emitNodes.Get(node)
|
|
emitNode.commentRange = loc
|
|
emitNode.flags |= hasCommentRange
|
|
}
|
|
|
|
// Sets the range to use for a node when emitting comments.
|
|
func (c *EmitContext) AssignCommentRange(to *ast.Node, from *ast.Node) {
|
|
c.SetCommentRange(to, c.CommentRange(from))
|
|
}
|
|
|
|
// Gets the range to use for a node when emitting source maps.
|
|
func (c *EmitContext) SourceMapRange(node *ast.Node) core.TextRange {
|
|
if emitNode := c.emitNodes.TryGet(node); emitNode != nil && emitNode.flags&hasSourceMapRange != 0 {
|
|
return emitNode.sourceMapRange
|
|
}
|
|
return node.Loc
|
|
}
|
|
|
|
// Sets the range to use for a node when emitting source maps.
|
|
func (c *EmitContext) SetSourceMapRange(node *ast.Node, loc core.TextRange) {
|
|
emitNode := c.emitNodes.Get(node)
|
|
emitNode.sourceMapRange = loc
|
|
emitNode.flags |= hasSourceMapRange
|
|
}
|
|
|
|
// Sets the range to use for a node when emitting source maps.
|
|
func (c *EmitContext) AssignSourceMapRange(to *ast.Node, from *ast.Node) {
|
|
c.SetSourceMapRange(to, c.SourceMapRange(from))
|
|
}
|
|
|
|
// Sets the range to use for a node when emitting comments and source maps.
|
|
func (c *EmitContext) AssignCommentAndSourceMapRanges(to *ast.Node, from *ast.Node) {
|
|
emitNode := c.emitNodes.Get(to)
|
|
commentRange := c.CommentRange(from)
|
|
sourceMapRange := c.SourceMapRange(from)
|
|
emitNode.commentRange = commentRange
|
|
emitNode.sourceMapRange = sourceMapRange
|
|
emitNode.flags |= hasCommentRange | hasSourceMapRange
|
|
}
|
|
|
|
// Gets the range for a token of a node when emitting source maps.
|
|
func (c *EmitContext) TokenSourceMapRange(node *ast.Node, kind ast.Kind) (core.TextRange, bool) {
|
|
if emitNode := c.emitNodes.TryGet(node); emitNode != nil && emitNode.tokenSourceMapRanges != nil {
|
|
if loc, ok := emitNode.tokenSourceMapRanges[kind]; ok {
|
|
return loc, true
|
|
}
|
|
}
|
|
return core.TextRange{}, false
|
|
}
|
|
|
|
// Sets the range for a token of a node when emitting source maps.
|
|
func (c *EmitContext) SetTokenSourceMapRange(node *ast.Node, kind ast.Kind, loc core.TextRange) {
|
|
emitNode := c.emitNodes.Get(node)
|
|
if emitNode.tokenSourceMapRanges == nil {
|
|
emitNode.tokenSourceMapRanges = make(map[ast.Kind]core.TextRange)
|
|
}
|
|
emitNode.tokenSourceMapRanges[kind] = loc
|
|
}
|
|
|
|
func (c *EmitContext) AssignedName(node *ast.Node) *ast.Expression {
|
|
return c.assignedName[node]
|
|
}
|
|
|
|
func (c *EmitContext) SetAssignedName(node *ast.Node, name *ast.Expression) {
|
|
if c.assignedName == nil {
|
|
c.assignedName = make(map[*ast.Node]*ast.Expression)
|
|
}
|
|
c.assignedName[node] = name
|
|
}
|
|
|
|
func (c *EmitContext) ClassThis(node *ast.Node) *ast.Expression {
|
|
return c.classThis[node]
|
|
}
|
|
|
|
func (c *EmitContext) SetClassThis(node *ast.Node, classThis *ast.IdentifierNode) {
|
|
if c.classThis == nil {
|
|
c.classThis = make(map[*ast.Node]*ast.Expression)
|
|
}
|
|
c.classThis[node] = classThis
|
|
}
|
|
|
|
func (c *EmitContext) RequestEmitHelper(helper *EmitHelper) {
|
|
if helper.Scoped {
|
|
panic("Cannot request a scoped emit helper")
|
|
}
|
|
for _, h := range helper.Dependencies {
|
|
c.RequestEmitHelper(h)
|
|
}
|
|
c.emitHelpers.Add(helper)
|
|
}
|
|
|
|
func (c *EmitContext) ReadEmitHelpers() []*EmitHelper {
|
|
helpers := slices.Collect(c.emitHelpers.Values())
|
|
c.emitHelpers.Clear()
|
|
return helpers
|
|
}
|
|
|
|
func (c *EmitContext) AddEmitHelper(node *ast.Node, helper ...*EmitHelper) {
|
|
emitNode := c.emitNodes.Get(node)
|
|
emitNode.helpers = append(emitNode.helpers, helper...)
|
|
}
|
|
|
|
func (c *EmitContext) MoveEmitHelpers(source *ast.Node, target *ast.Node, predicate func(helper *EmitHelper) bool) {
|
|
sourceEmitNode := c.emitNodes.TryGet(source)
|
|
if sourceEmitNode == nil {
|
|
return
|
|
}
|
|
sourceEmitHelpers := sourceEmitNode.helpers
|
|
if len(sourceEmitHelpers) == 0 {
|
|
return
|
|
}
|
|
|
|
targetEmitNode := c.emitNodes.Get(target)
|
|
helpersRemoved := 0
|
|
for i := range sourceEmitHelpers {
|
|
helper := sourceEmitHelpers[i]
|
|
if predicate(helper) {
|
|
helpersRemoved++
|
|
targetEmitNode.helpers = core.AppendIfUnique(targetEmitNode.helpers, helper)
|
|
} else if helpersRemoved > 0 {
|
|
sourceEmitHelpers[i-helpersRemoved] = helper
|
|
}
|
|
}
|
|
|
|
if helpersRemoved > 0 {
|
|
sourceEmitHelpers = sourceEmitHelpers[:len(sourceEmitHelpers)-helpersRemoved]
|
|
sourceEmitNode.helpers = sourceEmitHelpers
|
|
}
|
|
}
|
|
|
|
func (c *EmitContext) GetEmitHelpers(node *ast.Node) []*EmitHelper {
|
|
emitNode := c.emitNodes.TryGet(node)
|
|
if emitNode != nil {
|
|
return emitNode.helpers
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *EmitContext) GetExternalHelpersModuleName(node *ast.SourceFile) *ast.IdentifierNode {
|
|
if parseNode := c.ParseNode(node.AsNode()); parseNode != nil {
|
|
if emitNode := c.emitNodes.TryGet(parseNode); emitNode != nil {
|
|
return emitNode.externalHelpersModuleName
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *EmitContext) SetExternalHelpersModuleName(node *ast.SourceFile, name *ast.IdentifierNode) {
|
|
parseNode := c.ParseNode(node.AsNode())
|
|
if parseNode == nil {
|
|
panic("Node must be a parse tree node or have an Original pointer to a parse tree node.")
|
|
}
|
|
|
|
emitNode := c.emitNodes.Get(parseNode)
|
|
emitNode.externalHelpersModuleName = name
|
|
}
|
|
|
|
func (c *EmitContext) HasRecordedExternalHelpers(node *ast.SourceFile) bool {
|
|
if parseNode := c.ParseNode(node.AsNode()); parseNode != nil {
|
|
emitNode := c.emitNodes.TryGet(parseNode)
|
|
return emitNode != nil && (emitNode.externalHelpersModuleName != nil || emitNode.emitFlags&EFExternalHelpers != 0)
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (c *EmitContext) IsCallToHelper(firstSegment *ast.Expression, helperName string) bool {
|
|
return ast.IsCallExpression(firstSegment) &&
|
|
ast.IsIdentifier(firstSegment.Expression()) &&
|
|
(c.EmitFlags(firstSegment.Expression())&EFHelperName) != 0 &&
|
|
firstSegment.Expression().Text() == helperName
|
|
}
|
|
|
|
//
|
|
// Visitor Hooks
|
|
//
|
|
|
|
func (c *EmitContext) VisitVariableEnvironment(nodes *ast.StatementList, visitor *ast.NodeVisitor) *ast.StatementList {
|
|
c.StartVariableEnvironment()
|
|
return c.EndAndMergeVariableEnvironmentList(visitor.VisitNodes(nodes))
|
|
}
|
|
|
|
func (c *EmitContext) VisitParameters(nodes *ast.ParameterList, visitor *ast.NodeVisitor) *ast.ParameterList {
|
|
c.StartVariableEnvironment()
|
|
scope := c.varScopeStack.Peek()
|
|
oldFlags := scope.flags
|
|
scope.flags |= environmentFlagsInParameters
|
|
nodes = visitor.VisitNodes(nodes)
|
|
|
|
// As of ES2015, any runtime execution of that occurs in for a parameter (such as evaluating an
|
|
// initializer or a binding pattern), occurs in its own lexical scope. As a result, any expression
|
|
// that we might transform that introduces a temporary variable would fail as the temporary variable
|
|
// exists in a different lexical scope. To address this, we move any binding patterns and initializers
|
|
// in a parameter list to the body if we detect a variable being hoisted while visiting a parameter list
|
|
// when the emit target is greater than ES2015. (Which is now all targets.)
|
|
if scope.flags&environmentFlagsVariablesHoistedInParameters != 0 {
|
|
nodes = c.addDefaultValueAssignmentsIfNeeded(nodes)
|
|
}
|
|
scope.flags = oldFlags
|
|
// !!! c.suspendVariableEnvironment()
|
|
return nodes
|
|
}
|
|
|
|
func (c *EmitContext) addDefaultValueAssignmentsIfNeeded(nodeList *ast.ParameterList) *ast.ParameterList {
|
|
if nodeList == nil {
|
|
return nodeList
|
|
}
|
|
var result []*ast.Node
|
|
nodes := nodeList.Nodes
|
|
for i, parameter := range nodes {
|
|
updated := c.addDefaultValueAssignmentIfNeeded(parameter.AsParameterDeclaration())
|
|
if updated != parameter {
|
|
if result == nil {
|
|
result = slices.Clone(nodes)
|
|
}
|
|
result[i] = updated
|
|
}
|
|
}
|
|
if result != nil {
|
|
res := c.Factory.NewNodeList(result)
|
|
res.Loc = nodeList.Loc
|
|
return res
|
|
}
|
|
return nodeList
|
|
}
|
|
|
|
func (c *EmitContext) addDefaultValueAssignmentIfNeeded(parameter *ast.ParameterDeclaration) *ast.Node {
|
|
// A rest parameter cannot have a binding pattern or an initializer,
|
|
// so let's just ignore it.
|
|
if parameter.DotDotDotToken != nil {
|
|
return parameter.AsNode()
|
|
} else if ast.IsBindingPattern(parameter.Name()) {
|
|
return c.addDefaultValueAssignmentForBindingPattern(parameter)
|
|
} else if parameter.Initializer != nil {
|
|
return c.addDefaultValueAssignmentForInitializer(parameter, parameter.Name(), parameter.Initializer)
|
|
}
|
|
return parameter.AsNode()
|
|
}
|
|
|
|
func (c *EmitContext) addDefaultValueAssignmentForBindingPattern(parameter *ast.ParameterDeclaration) *ast.Node {
|
|
var initNode *ast.Node
|
|
if parameter.Initializer != nil {
|
|
initNode = c.Factory.NewConditionalExpression(
|
|
c.Factory.NewStrictEqualityExpression(
|
|
c.Factory.NewGeneratedNameForNode(parameter.AsNode()),
|
|
c.Factory.NewVoidZeroExpression(),
|
|
),
|
|
c.Factory.NewToken(ast.KindQuestionToken),
|
|
parameter.Initializer,
|
|
c.Factory.NewToken(ast.KindColonToken),
|
|
c.Factory.NewGeneratedNameForNode(parameter.AsNode()),
|
|
)
|
|
} else {
|
|
initNode = c.Factory.NewGeneratedNameForNode(parameter.AsNode())
|
|
}
|
|
c.AddInitializationStatement(c.Factory.NewVariableStatement(
|
|
nil,
|
|
c.Factory.NewVariableDeclarationList(ast.NodeFlagsNone, c.Factory.NewNodeList([]*ast.Node{c.Factory.NewVariableDeclaration(
|
|
parameter.Name(),
|
|
nil,
|
|
parameter.Type,
|
|
initNode,
|
|
)})),
|
|
))
|
|
return c.Factory.UpdateParameterDeclaration(
|
|
parameter,
|
|
parameter.Modifiers(),
|
|
parameter.DotDotDotToken,
|
|
c.Factory.NewGeneratedNameForNode(parameter.AsNode()),
|
|
parameter.QuestionToken,
|
|
parameter.Type,
|
|
nil,
|
|
)
|
|
}
|
|
|
|
func (c *EmitContext) addDefaultValueAssignmentForInitializer(parameter *ast.ParameterDeclaration, name *ast.Node, initializer *ast.Node) *ast.Node {
|
|
c.AddEmitFlags(initializer, EFNoSourceMap|EFNoComments)
|
|
nameClone := name.Clone(c.Factory)
|
|
c.AddEmitFlags(nameClone, EFNoSourceMap)
|
|
initAssignment := c.Factory.NewAssignmentExpression(
|
|
nameClone,
|
|
initializer,
|
|
)
|
|
initAssignment.Loc = parameter.Loc
|
|
c.AddEmitFlags(initAssignment, EFNoComments)
|
|
initBlock := c.Factory.NewBlock(c.Factory.NewNodeList([]*ast.Node{c.Factory.NewExpressionStatement(initAssignment)}), false)
|
|
initBlock.Loc = parameter.Loc
|
|
c.AddEmitFlags(initBlock, EFSingleLine|EFNoTrailingSourceMap|EFNoTokenSourceMaps|EFNoComments)
|
|
c.AddInitializationStatement(c.Factory.NewIfStatement(
|
|
c.Factory.NewTypeCheck(name.Clone(c.Factory), "undefined"),
|
|
initBlock,
|
|
nil,
|
|
))
|
|
return c.Factory.UpdateParameterDeclaration(
|
|
parameter,
|
|
parameter.Modifiers(),
|
|
parameter.DotDotDotToken,
|
|
parameter.Name(),
|
|
parameter.QuestionToken,
|
|
parameter.Type,
|
|
nil,
|
|
)
|
|
}
|
|
|
|
func (c *EmitContext) AddInitializationStatement(node *ast.Node) {
|
|
scope := c.varScopeStack.Peek()
|
|
if scope == nil {
|
|
panic("Tried to add an initialization statement without a surrounding variable scope")
|
|
}
|
|
c.AddEmitFlags(node, EFCustomPrologue)
|
|
scope.initializationStatements = append(scope.initializationStatements, node)
|
|
}
|
|
|
|
func (c *EmitContext) VisitFunctionBody(node *ast.BlockOrExpression, visitor *ast.NodeVisitor) *ast.BlockOrExpression {
|
|
// !!! c.resumeVariableEnvironment()
|
|
updated := visitor.VisitNode(node)
|
|
declarations := c.EndVariableEnvironment()
|
|
if len(declarations) == 0 {
|
|
return updated
|
|
}
|
|
|
|
if updated == nil {
|
|
return c.Factory.NewBlock(c.Factory.NewNodeList(declarations), true /*multiLine*/)
|
|
}
|
|
|
|
if !ast.IsBlock(updated) {
|
|
statements := c.MergeEnvironment([]*ast.Statement{c.Factory.NewReturnStatement(updated)}, declarations)
|
|
return c.Factory.NewBlock(c.Factory.NewNodeList(statements), true /*multiLine*/)
|
|
}
|
|
|
|
return c.Factory.UpdateBlock(
|
|
updated.AsBlock(),
|
|
c.MergeEnvironmentList(updated.AsBlock().Statements, declarations),
|
|
)
|
|
}
|
|
|
|
func (c *EmitContext) VisitIterationBody(body *ast.Statement, visitor *ast.NodeVisitor) *ast.Statement {
|
|
if body == nil {
|
|
return nil
|
|
}
|
|
|
|
c.StartLexicalEnvironment()
|
|
updated := c.VisitEmbeddedStatement(body, visitor)
|
|
if updated == nil {
|
|
panic("Expected visitor to return a statement.")
|
|
}
|
|
|
|
statements := c.EndLexicalEnvironment()
|
|
if len(statements) > 0 {
|
|
if ast.IsBlock(updated) {
|
|
statements = append(statements, updated.AsBlock().Statements.Nodes...)
|
|
statementsList := c.Factory.NewNodeList(statements)
|
|
statementsList.Loc = updated.AsBlock().Statements.Loc
|
|
return c.Factory.UpdateBlock(updated.AsBlock(), statementsList)
|
|
}
|
|
statements = append(statements, updated)
|
|
return c.Factory.NewBlock(c.Factory.NewNodeList(statements), true /*multiLine*/)
|
|
}
|
|
|
|
return updated
|
|
}
|
|
|
|
func (c *EmitContext) VisitEmbeddedStatement(node *ast.Statement, visitor *ast.NodeVisitor) *ast.Statement {
|
|
embeddedStatement := visitor.VisitEmbeddedStatement(node)
|
|
if embeddedStatement == nil {
|
|
return nil
|
|
}
|
|
if ast.IsNotEmittedStatement(embeddedStatement) {
|
|
emptyStatement := visitor.Factory.NewEmptyStatement()
|
|
emptyStatement.Loc = node.Loc
|
|
c.SetOriginal(emptyStatement, node)
|
|
c.AssignCommentRange(emptyStatement, node)
|
|
return emptyStatement
|
|
}
|
|
return embeddedStatement
|
|
}
|
|
|
|
func (c *EmitContext) SetSyntheticLeadingComments(node *ast.Node, comments []SynthesizedComment) *ast.Node {
|
|
c.emitNodes.Get(node).leadingComments = comments
|
|
return node
|
|
}
|
|
|
|
func (c *EmitContext) AddSyntheticLeadingComment(node *ast.Node, kind ast.Kind, text string, hasTrailingNewLine bool) *ast.Node {
|
|
c.emitNodes.Get(node).leadingComments = append(c.emitNodes.Get(node).leadingComments, SynthesizedComment{Kind: kind, Loc: core.NewTextRange(-1, -1), HasTrailingNewLine: hasTrailingNewLine, Text: text})
|
|
return node
|
|
}
|
|
|
|
func (c *EmitContext) GetSyntheticLeadingComments(node *ast.Node) []SynthesizedComment {
|
|
if c.emitNodes.Has(node) {
|
|
return c.emitNodes.Get(node).leadingComments
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *EmitContext) SetSyntheticTrailingComments(node *ast.Node, comments []SynthesizedComment) *ast.Node {
|
|
c.emitNodes.Get(node).trailingComments = comments
|
|
return node
|
|
}
|
|
|
|
func (c *EmitContext) AddSyntheticTrailingComment(node *ast.Node, kind ast.Kind, text string, hasTrailingNewLine bool) *ast.Node {
|
|
c.emitNodes.Get(node).trailingComments = append(c.emitNodes.Get(node).trailingComments, SynthesizedComment{Kind: kind, Loc: core.NewTextRange(-1, -1), HasTrailingNewLine: hasTrailingNewLine, Text: text})
|
|
return node
|
|
}
|
|
|
|
func (c *EmitContext) GetSyntheticTrailingComments(node *ast.Node) []SynthesizedComment {
|
|
if c.emitNodes.Has(node) {
|
|
return c.emitNodes.Get(node).trailingComments
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *EmitContext) NewNotEmittedStatement(node *ast.Node) *ast.Statement {
|
|
statement := c.Factory.NewNotEmittedStatement()
|
|
statement.Loc = node.Loc
|
|
c.SetOriginal(statement, node)
|
|
c.AssignCommentRange(statement, node)
|
|
return statement
|
|
}
|