remove unused packages

This commit is contained in:
Egor Aristov 2025-10-15 19:21:44 +03:00
parent d14eb0a9b7
commit 87a7c7e9f5
Signed by: egor3f
GPG Key ID: 40482A264AAEC85F
18 changed files with 0 additions and 13335 deletions

View File

@ -1,231 +0,0 @@
package printer
import (
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/scanner"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/stringutil"
)
type ChangeTrackerWriter struct {
textWriter
lastNonTriviaPosition int
pos map[triviaPositionKey]int
end map[triviaPositionKey]int
}
type triviaPositionKey interface { // *astNode | *ast.NodeList
Pos() int
End() int
}
func NewChangeTrackerWriter(newline string) *ChangeTrackerWriter {
ctw := &ChangeTrackerWriter{
textWriter: textWriter{newLine: newline},
lastNonTriviaPosition: 0,
pos: map[triviaPositionKey]int{},
end: map[triviaPositionKey]int{},
}
ctw.textWriter.Clear()
return ctw
}
func (ct *ChangeTrackerWriter) GetPrintHandlers() PrintHandlers {
return PrintHandlers{
OnBeforeEmitNode: func(nodeOpt *ast.Node) {
if nodeOpt != nil {
ct.setPos(nodeOpt)
}
},
OnAfterEmitNode: func(nodeOpt *ast.Node) {
if nodeOpt != nil {
ct.setEnd(nodeOpt)
}
},
OnBeforeEmitNodeList: func(nodesOpt *ast.NodeList) {
if nodesOpt != nil {
ct.setPos(nodesOpt)
}
},
OnAfterEmitNodeList: func(nodesOpt *ast.NodeList) {
if nodesOpt != nil {
ct.setEnd(nodesOpt)
}
},
OnBeforeEmitToken: func(nodeOpt *ast.TokenNode) {
if nodeOpt != nil {
ct.setPos(nodeOpt)
}
},
OnAfterEmitToken: func(nodeOpt *ast.TokenNode) {
if nodeOpt != nil {
ct.setEnd(nodeOpt)
}
},
}
}
func (ct *ChangeTrackerWriter) setPos(node triviaPositionKey) {
ct.pos[node] = ct.lastNonTriviaPosition
}
func (ct *ChangeTrackerWriter) setEnd(node triviaPositionKey) {
ct.end[node] = ct.lastNonTriviaPosition
}
func (ct *ChangeTrackerWriter) getPos(node triviaPositionKey) int {
return ct.pos[node]
}
func (ct *ChangeTrackerWriter) getEnd(node triviaPositionKey) int {
return ct.end[node]
}
func (ct *ChangeTrackerWriter) setLastNonTriviaPosition(s string, force bool) {
if force || scanner.SkipTrivia(s, 0) != len(s) {
ct.lastNonTriviaPosition = ct.textWriter.GetTextPos()
i := 0
for stringutil.IsWhiteSpaceLike(rune(s[len(s)-i-1])) {
i++
}
// trim trailing whitespaces
ct.lastNonTriviaPosition -= i
}
}
func (ct *ChangeTrackerWriter) AssignPositionsToNode(node *ast.Node, factory *ast.NodeFactory) *ast.Node {
var visitor *ast.NodeVisitor
visitor = &ast.NodeVisitor{
Visit: func(n *ast.Node) *ast.Node { return ct.assignPositionsToNodeWorker(n, visitor) },
Factory: factory,
Hooks: ast.NodeVisitorHooks{
VisitNode: ct.assignPositionsToNodeWorker,
VisitNodes: ct.assignPositionsToNodeArray,
VisitToken: ct.assignPositionsToNodeWorker,
VisitModifiers: func(modifiers *ast.ModifierList, v *ast.NodeVisitor) *ast.ModifierList {
if modifiers != nil {
ct.assignPositionsToNodeArray(&modifiers.NodeList, v)
}
return modifiers
},
},
}
return ct.assignPositionsToNodeWorker(node, visitor)
}
func (ct *ChangeTrackerWriter) assignPositionsToNodeWorker(
node *ast.Node,
v *ast.NodeVisitor,
) *ast.Node {
if node == nil {
return node
}
visited := node.VisitEachChild(v)
// create proxy node for non synthesized nodes
newNode := visited
if !ast.NodeIsSynthesized(visited) {
newNode = visited.Clone(v.Factory)
}
newNode.ForEachChild(func(child *ast.Node) bool {
child.Parent = newNode
return true
})
newNode.Loc = core.NewTextRange(ct.getPos(node), ct.getEnd(node))
return newNode
}
func (ct *ChangeTrackerWriter) assignPositionsToNodeArray(
nodes *ast.NodeList,
v *ast.NodeVisitor,
) *ast.NodeList {
visited := v.VisitNodes(nodes)
if visited == nil {
return visited
}
if nodes == nil {
// Debug.assert(nodes);
panic("if nodes is nil, visited should not be nil")
}
// clone nodearray if necessary
nodeArray := visited
if visited == nodes {
nodeArray = visited.Clone(v.Factory)
}
nodeArray.Loc = core.NewTextRange(ct.getPos(nodes), ct.getEnd(nodes))
return nodeArray
}
func (ct *ChangeTrackerWriter) Write(text string) {
ct.textWriter.Write(text)
ct.setLastNonTriviaPosition(text, false)
}
func (ct *ChangeTrackerWriter) WriteTrailingSemicolon(text string) {
ct.textWriter.WriteTrailingSemicolon(text)
ct.setLastNonTriviaPosition(text, false)
}
func (ct *ChangeTrackerWriter) WriteComment(text string) { ct.textWriter.WriteComment(text) }
func (ct *ChangeTrackerWriter) WriteKeyword(text string) {
ct.textWriter.WriteKeyword(text)
ct.setLastNonTriviaPosition(text, false)
}
func (ct *ChangeTrackerWriter) WriteOperator(text string) {
ct.textWriter.WriteOperator(text)
ct.setLastNonTriviaPosition(text, false)
}
func (ct *ChangeTrackerWriter) WritePunctuation(text string) {
ct.textWriter.WritePunctuation(text)
ct.setLastNonTriviaPosition(text, false)
}
func (ct *ChangeTrackerWriter) WriteSpace(text string) {
ct.textWriter.WriteSpace(text)
ct.setLastNonTriviaPosition(text, false)
}
func (ct *ChangeTrackerWriter) WriteStringLiteral(text string) {
ct.textWriter.WriteStringLiteral(text)
ct.setLastNonTriviaPosition(text, false)
}
func (ct *ChangeTrackerWriter) WriteParameter(text string) {
ct.textWriter.WriteParameter(text)
ct.setLastNonTriviaPosition(text, false)
}
func (ct *ChangeTrackerWriter) WriteProperty(text string) {
ct.textWriter.WriteProperty(text)
ct.setLastNonTriviaPosition(text, false)
}
func (ct *ChangeTrackerWriter) WriteSymbol(text string, symbol *ast.Symbol) {
ct.textWriter.WriteSymbol(text, symbol)
ct.setLastNonTriviaPosition(text, false)
}
func (ct *ChangeTrackerWriter) WriteLine() { ct.textWriter.WriteLine() }
func (ct *ChangeTrackerWriter) WriteLineForce(force bool) { ct.textWriter.WriteLineForce(force) }
func (ct *ChangeTrackerWriter) IncreaseIndent() { ct.textWriter.IncreaseIndent() }
func (ct *ChangeTrackerWriter) DecreaseIndent() { ct.textWriter.DecreaseIndent() }
func (ct *ChangeTrackerWriter) Clear() { ct.textWriter.Clear(); ct.lastNonTriviaPosition = 0 }
func (ct *ChangeTrackerWriter) String() string { return ct.textWriter.String() }
func (ct *ChangeTrackerWriter) RawWrite(s string) {
ct.textWriter.RawWrite(s)
ct.setLastNonTriviaPosition(s, false)
}
func (ct *ChangeTrackerWriter) WriteLiteral(s string) {
ct.textWriter.WriteLiteral(s)
ct.setLastNonTriviaPosition(s, true)
}
func (ct *ChangeTrackerWriter) GetTextPos() int { return ct.textWriter.GetTextPos() }
func (ct *ChangeTrackerWriter) GetLine() int { return ct.textWriter.GetLine() }
func (ct *ChangeTrackerWriter) GetColumn() int { return ct.textWriter.GetColumn() }
func (ct *ChangeTrackerWriter) GetIndent() int { return ct.textWriter.GetIndent() }
func (ct *ChangeTrackerWriter) IsAtStartOfLine() bool { return ct.textWriter.IsAtStartOfLine() }
func (ct *ChangeTrackerWriter) HasTrailingComment() bool { return ct.textWriter.HasTrailingComment() }
func (ct *ChangeTrackerWriter) HasTrailingWhitespace() bool {
return ct.textWriter.HasTrailingWhitespace()
}

View File

@ -1,987 +0,0 @@
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
}

View File

@ -1,37 +0,0 @@
package printer
type EmitFlags uint32
const (
EFSingleLine EmitFlags = 1 << iota // The contents of this node should be emitted on a single line.
EFMultiLine // The contents of this node should be emitted on multiple lines.
EFNoLeadingSourceMap // Do not emit a leading source map location for this node.
EFNoTrailingSourceMap // Do not emit a trailing source map location for this node.
EFNoNestedSourceMaps // Do not emit source map locations for children of this node.
EFNoTokenLeadingSourceMaps // Do not emit leading source map location for token nodes.
EFNoTokenTrailingSourceMaps // Do not emit trailing source map location for token nodes.
EFNoLeadingComments // Do not emit leading comments for this node.
EFNoTrailingComments // Do not emit trailing comments for this node.
EFNoNestedComments // Do not emit nested comments for children of this node.
EFHelperName // The Identifier refers to an *unscoped* emit helper (one that is emitted at the top of the file)
EFExportName // Ensure an export prefix is added for an identifier that points to an exported declaration with a local name (see SymbolFlags.ExportHasLocal).
EFLocalName // Ensure an export prefix is not added for an identifier that points to an exported declaration.
EFInternalName // The name is internal to an ES5 class body function.
EFIndented // Adds an explicit extra indentation level for class and function bodies when printing (used to match old emitter).
EFNoIndentation // Do not indent the node.
EFReuseTempVariableScope // Reuse the existing temp variable scope during emit.
EFCustomPrologue // Treat the statement as if it were a prologue directive (NOTE: Prologue directives are *not* transformed).
EFNoHoisting // Do not hoist this declaration in --module system
EFNoAsciiEscaping // When synthesizing nodes that lack an original node or textSourceNode, we want to write the text on the node with ASCII escaping substitutions.
EFExternalHelpers // This source file has external helpers
EFNeverApplyImportHelper // Do not apply an import helper to this node
EFStartOnNewLine // Start this node on a new line
EFIndirectCall // Emit CallExpression as an indirect call: `(0, f)()`
)
const (
EFNone EmitFlags = 0
EFNoSourceMap = EFNoLeadingSourceMap | EFNoTrailingSourceMap // Do not emit a source map location for this node.
EFNoTokenSourceMaps = EFNoTokenLeadingSourceMaps | EFNoTokenTrailingSourceMaps // Do not emit source map locations for tokens of this node.
EFNoComments = EFNoLeadingComments | EFNoTrailingComments // Do not emit comments for this node.
)

View File

@ -1,23 +0,0 @@
package printer
import (
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tsoptions"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath"
)
// NOTE: EmitHost operations must be thread-safe
type EmitHost interface {
Options() *core.CompilerOptions
SourceFiles() []*ast.SourceFile
UseCaseSensitiveFileNames() bool
GetCurrentDirectory() string
CommonSourceDirectory() string
IsEmitBlocked(file string) bool
WriteFile(fileName string, text string, writeByteOrderMark bool) error
GetEmitModuleFormatOfFile(file ast.HasFileName) core.ModuleKind
GetEmitResolver() EmitResolver
GetProjectReferenceFromSource(path tspath.Path) *tsoptions.SourceOutputAndProjectReference
IsSourceFileFromExternalLibrary(file *ast.SourceFile) bool
}

View File

@ -1,68 +0,0 @@
package printer
import (
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/binder"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/evaluator"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/nodebuilder"
)
type SymbolAccessibility int32
const (
SymbolAccessibilityAccessible SymbolAccessibility = iota
SymbolAccessibilityNotAccessible
SymbolAccessibilityCannotBeNamed
SymbolAccessibilityNotResolved
)
type SymbolAccessibilityResult struct {
Accessibility SymbolAccessibility
AliasesToMakeVisible []*ast.Node // aliases that need to have this symbol visible
ErrorSymbolName string // Optional - symbol name that results in error
ErrorNode *ast.Node // Optional - node that results in error
ErrorModuleName string // Optional - If the symbol is not visible from module, module's name
}
type EmitResolver interface {
binder.ReferenceResolver
IsReferencedAliasDeclaration(node *ast.Node) bool
IsValueAliasDeclaration(node *ast.Node) bool
IsTopLevelValueImportEqualsWithEntityName(node *ast.Node) bool
MarkLinkedReferencesRecursively(file *ast.SourceFile)
GetExternalModuleFileFromDeclaration(node *ast.Node) *ast.SourceFile
GetEffectiveDeclarationFlags(node *ast.Node, flags ast.ModifierFlags) ast.ModifierFlags
GetResolutionModeOverride(node *ast.Node) core.ResolutionMode
// const enum inlining
GetConstantValue(node *ast.Node) any
// JSX Emit
GetJsxFactoryEntity(location *ast.Node) *ast.Node
GetJsxFragmentFactoryEntity(location *ast.Node) *ast.Node
SetReferencedImportDeclaration(node *ast.IdentifierNode, ref *ast.Declaration) // for overriding the reference resolver behavior for generated identifiers
// declaration emit checker functionality projections
PrecalculateDeclarationEmitVisibility(file *ast.SourceFile)
IsSymbolAccessible(symbol *ast.Symbol, enclosingDeclaration *ast.Node, meaning ast.SymbolFlags, shouldComputeAliasToMarkVisible bool) SymbolAccessibilityResult
IsEntityNameVisible(entityName *ast.Node, enclosingDeclaration *ast.Node) SymbolAccessibilityResult // previously SymbolVisibilityResult in strada - ErrorModuleName never set
IsExpandoFunctionDeclaration(node *ast.Node) bool
IsLiteralConstDeclaration(node *ast.Node) bool
RequiresAddingImplicitUndefined(node *ast.Node, symbol *ast.Symbol, enclosingDeclaration *ast.Node) bool
IsDeclarationVisible(node *ast.Node) bool
IsImportRequiredByAugmentation(decl *ast.ImportDeclaration) bool
IsDefinitelyReferenceToGlobalSymbolObject(node *ast.Node) bool
IsImplementationOfOverload(node *ast.SignatureDeclaration) bool
GetEnumMemberValue(node *ast.Node) evaluator.Result
IsLateBound(node *ast.Node) bool
IsOptionalParameter(node *ast.Node) bool
// Node construction for declaration emit
CreateTypeOfDeclaration(emitContext *EmitContext, declaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node
CreateReturnTypeOfSignatureDeclaration(emitContext *EmitContext, signatureDeclaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node
CreateTypeParametersOfSignatureDeclaration(emitContext *EmitContext, signatureDeclaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) []*ast.Node
CreateLiteralConstValue(emitContext *EmitContext, node *ast.Node, tracker nodebuilder.SymbolTracker) *ast.Node
CreateTypeOfExpression(emitContext *EmitContext, expression *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node
CreateLateBoundIndexSignatures(emitContext *EmitContext, container *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) []*ast.Node
}

View File

@ -1,33 +0,0 @@
package printer
import "efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
// Externally opaque interface for printing text
type EmitTextWriter interface {
Write(s string)
WriteTrailingSemicolon(text string)
WriteComment(text string)
WriteKeyword(text string)
WriteOperator(text string)
WritePunctuation(text string)
WriteSpace(text string)
WriteStringLiteral(text string)
WriteParameter(text string)
WriteProperty(text string)
WriteSymbol(text string, symbol *ast.Symbol)
WriteLine()
WriteLineForce(force bool)
IncreaseIndent()
DecreaseIndent()
Clear()
String() string
RawWrite(s string)
WriteLiteral(s string)
GetTextPos() int
GetLine() int
GetColumn() int
GetIndent() int
IsAtStartOfLine() bool
HasTrailingComment() bool
HasTrailingWhitespace() bool
}

View File

@ -1,693 +0,0 @@
package printer
import (
"fmt"
"strconv"
"strings"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/debug"
)
type NodeFactory struct {
ast.NodeFactory
emitContext *EmitContext
}
func NewNodeFactory(context *EmitContext) *NodeFactory {
return &NodeFactory{
NodeFactory: *ast.NewNodeFactory(ast.NodeFactoryHooks{
OnCreate: context.onCreate,
OnUpdate: context.onUpdate,
OnClone: context.onClone,
}),
emitContext: context,
}
}
func (f *NodeFactory) newGeneratedIdentifier(kind GeneratedIdentifierFlags, text string, node *ast.Node, options AutoGenerateOptions) *ast.IdentifierNode {
id := AutoGenerateId(nextAutoGenerateId.Add(1))
if len(text) == 0 {
switch {
case node == nil:
text = fmt.Sprintf("(auto@%d)", id)
case ast.IsMemberName(node):
text = node.Text()
default:
text = fmt.Sprintf("(generated@%v)", ast.GetNodeId(f.emitContext.getNodeForGeneratedNameWorker(node, id)))
}
text = FormatGeneratedName(false /*privateName*/, options.Prefix, text, options.Suffix)
}
name := f.NewIdentifier(text)
autoGenerate := &AutoGenerateInfo{
Id: id,
Flags: kind | (options.Flags & ^GeneratedIdentifierFlagsKindMask),
Prefix: options.Prefix,
Suffix: options.Suffix,
Node: node,
}
if f.emitContext.autoGenerate == nil {
f.emitContext.autoGenerate = make(map[*ast.MemberName]*AutoGenerateInfo)
}
f.emitContext.autoGenerate[name] = autoGenerate
return name
}
// Allocates a new temp variable name, but does not record it in the environment. It is recommended to pass this to either
// `AddVariableDeclaration` or `AddLexicalDeclaration` to ensure it is properly tracked, if you are not otherwise handling
// it yourself.
func (f *NodeFactory) NewTempVariable() *ast.IdentifierNode {
return f.NewTempVariableEx(AutoGenerateOptions{})
}
// Allocates a new temp variable name, but does not record it in the environment. It is recommended to pass this to either
// `AddVariableDeclaration` or `AddLexicalDeclaration` to ensure it is properly tracked, if you are not otherwise handling
// it yourself.
func (f *NodeFactory) NewTempVariableEx(options AutoGenerateOptions) *ast.IdentifierNode {
return f.newGeneratedIdentifier(GeneratedIdentifierFlagsAuto, "", nil /*node*/, options)
}
// Allocates a new loop variable name.
func (f *NodeFactory) NewLoopVariable() *ast.IdentifierNode {
return f.NewLoopVariableEx(AutoGenerateOptions{})
}
// Allocates a new loop variable name.
func (f *NodeFactory) NewLoopVariableEx(options AutoGenerateOptions) *ast.IdentifierNode {
return f.newGeneratedIdentifier(GeneratedIdentifierFlagsLoop, "", nil /*node*/, options)
}
// Allocates a new unique name based on the provided text.
func (f *NodeFactory) NewUniqueName(text string) *ast.IdentifierNode {
return f.NewUniqueNameEx(text, AutoGenerateOptions{})
}
// Allocates a new unique name based on the provided text.
func (f *NodeFactory) NewUniqueNameEx(text string, options AutoGenerateOptions) *ast.IdentifierNode {
return f.newGeneratedIdentifier(GeneratedIdentifierFlagsUnique, text, nil /*node*/, options)
}
// Allocates a new unique name based on the provided node.
func (f *NodeFactory) NewGeneratedNameForNode(node *ast.Node) *ast.IdentifierNode {
return f.NewGeneratedNameForNodeEx(node, AutoGenerateOptions{})
}
// Allocates a new unique name based on the provided node.
func (f *NodeFactory) NewGeneratedNameForNodeEx(node *ast.Node, options AutoGenerateOptions) *ast.IdentifierNode {
if len(options.Prefix) > 0 || len(options.Suffix) > 0 {
options.Flags |= GeneratedIdentifierFlagsOptimistic
}
return f.newGeneratedIdentifier(GeneratedIdentifierFlagsNode, "", node, options)
}
func (f *NodeFactory) newGeneratedPrivateIdentifier(kind GeneratedIdentifierFlags, text string, node *ast.Node, options AutoGenerateOptions) *ast.PrivateIdentifierNode {
id := AutoGenerateId(nextAutoGenerateId.Add(1))
if len(text) == 0 {
switch {
case node == nil:
text = fmt.Sprintf("(auto@%d)", id)
case ast.IsMemberName(node):
text = node.Text()
default:
text = fmt.Sprintf("(generated@%v)", ast.GetNodeId(f.emitContext.getNodeForGeneratedNameWorker(node, id)))
}
text = FormatGeneratedName(true /*privateName*/, options.Prefix, text, options.Suffix)
} else if !strings.HasPrefix(text, "#") {
panic("First character of private identifier must be #: " + text)
}
name := f.NewPrivateIdentifier(text)
autoGenerate := &AutoGenerateInfo{
Id: id,
Flags: kind | (options.Flags &^ GeneratedIdentifierFlagsKindMask),
Prefix: options.Prefix,
Suffix: options.Suffix,
Node: node,
}
if f.emitContext.autoGenerate == nil {
f.emitContext.autoGenerate = make(map[*ast.MemberName]*AutoGenerateInfo)
}
f.emitContext.autoGenerate[name] = autoGenerate
return name
}
// Allocates a new unique private name based on the provided text.
func (f *NodeFactory) NewUniquePrivateName(text string) *ast.PrivateIdentifierNode {
return f.NewUniquePrivateNameEx(text, AutoGenerateOptions{})
}
// Allocates a new unique private name based on the provided text.
func (f *NodeFactory) NewUniquePrivateNameEx(text string, options AutoGenerateOptions) *ast.PrivateIdentifierNode {
return f.newGeneratedPrivateIdentifier(GeneratedIdentifierFlagsUnique, text, nil /*node*/, options)
}
// Allocates a new unique private name based on the provided node.
func (f *NodeFactory) NewGeneratedPrivateNameForNode(node *ast.Node) *ast.PrivateIdentifierNode {
return f.NewGeneratedPrivateNameForNodeEx(node, AutoGenerateOptions{})
}
// Allocates a new unique private name based on the provided node.
func (f *NodeFactory) NewGeneratedPrivateNameForNodeEx(node *ast.Node, options AutoGenerateOptions) *ast.PrivateIdentifierNode {
if len(options.Prefix) > 0 || len(options.Suffix) > 0 {
options.Flags |= GeneratedIdentifierFlagsOptimistic
}
return f.newGeneratedPrivateIdentifier(GeneratedIdentifierFlagsNode, "", node, options)
}
// Allocates a new StringLiteral whose source text is derived from the provided node. This is often used to create a
// string representation of an Identifier or NumericLiteral.
func (f *NodeFactory) NewStringLiteralFromNode(textSourceNode *ast.Node) *ast.Node {
var text string
switch textSourceNode.Kind {
case ast.KindIdentifier,
ast.KindPrivateIdentifier,
ast.KindJsxNamespacedName,
ast.KindNumericLiteral,
ast.KindBigIntLiteral,
ast.KindNoSubstitutionTemplateLiteral,
ast.KindTemplateHead,
ast.KindTemplateMiddle,
ast.KindTemplateTail,
ast.KindRegularExpressionLiteral:
text = textSourceNode.Text()
}
node := f.NewStringLiteral(text)
if f.emitContext.textSource == nil {
f.emitContext.textSource = make(map[*ast.StringLiteralNode]*ast.Node)
}
f.emitContext.textSource[node] = textSourceNode
return node
}
//
// Common Tokens
//
func (f *NodeFactory) NewThisExpression() *ast.Expression {
return f.NewKeywordExpression(ast.KindThisKeyword)
}
func (f *NodeFactory) NewTrueExpression() *ast.Expression {
return f.NewKeywordExpression(ast.KindTrueKeyword)
}
func (f *NodeFactory) NewFalseExpression() *ast.Expression {
return f.NewKeywordExpression(ast.KindFalseKeyword)
}
//
// Common Operators
//
func (f *NodeFactory) NewCommaExpression(left *ast.Expression, right *ast.Expression) *ast.Expression {
return f.NewBinaryExpression(nil /*modifiers*/, left, nil /*typeNode*/, f.NewToken(ast.KindCommaToken), right)
}
func (f *NodeFactory) NewAssignmentExpression(left *ast.Expression, right *ast.Expression) *ast.Expression {
return f.NewBinaryExpression(nil /*modifiers*/, left, nil /*typeNode*/, f.NewToken(ast.KindEqualsToken), right)
}
func (f *NodeFactory) NewLogicalORExpression(left *ast.Expression, right *ast.Expression) *ast.Expression {
return f.NewBinaryExpression(nil /*modifiers*/, left, nil /*typeNode*/, f.NewToken(ast.KindBarBarToken), right)
}
// func (f *NodeFactory) NewLogicalANDExpression(left *ast.Expression, right *ast.Expression) *ast.Expression
// func (f *NodeFactory) NewBitwiseORExpression(left *ast.Expression, right *ast.Expression) *ast.Expression
// func (f *NodeFactory) NewBitwiseXORExpression(left *ast.Expression, right *ast.Expression) *ast.Expression
// func (f *NodeFactory) NewBitwiseANDExpression(left *ast.Expression, right *ast.Expression) *ast.Expression
func (f *NodeFactory) NewStrictEqualityExpression(left *ast.Expression, right *ast.Expression) *ast.Expression {
return f.NewBinaryExpression(nil /*modifiers*/, left, nil /*typeNode*/, f.NewToken(ast.KindEqualsEqualsEqualsToken), right)
}
func (f *NodeFactory) NewStrictInequalityExpression(left *ast.Expression, right *ast.Expression) *ast.Expression {
return f.NewBinaryExpression(nil /*modifiers*/, left, nil /*typeNode*/, f.NewToken(ast.KindExclamationEqualsEqualsToken), right)
}
//
// Compound Nodes
//
func (f *NodeFactory) NewVoidZeroExpression() *ast.Expression {
return f.NewVoidExpression(f.NewNumericLiteral("0"))
}
func flattenCommaElement(node *ast.Expression, expressions []*ast.Expression) []*ast.Expression {
if ast.IsBinaryExpression(node) && ast.NodeIsSynthesized(node) && node.AsBinaryExpression().OperatorToken.Kind == ast.KindCommaToken {
expressions = flattenCommaElement(node.AsBinaryExpression().Left, expressions)
expressions = flattenCommaElement(node.AsBinaryExpression().Right, expressions)
} else {
expressions = append(expressions, node)
}
return expressions
}
func flattenCommaElements(expressions []*ast.Expression) []*ast.Expression {
var result []*ast.Expression
for _, expression := range expressions {
result = flattenCommaElement(expression, result)
}
return result
}
// Converts a slice of expressions into a single comma-delimited expression. Returns nil if expressions is nil or empty.
// NOTE: Unlike Strada, the Corsa implementation does not currently use `ast.KindCommaListExpression`.
func (f *NodeFactory) InlineExpressions(expressions []*ast.Expression) *ast.Expression {
if len(expressions) == 0 {
return nil
}
if len(expressions) == 1 {
return expressions[0]
}
expressions = flattenCommaElements(expressions)
expression := expressions[0]
for _, next := range expressions[1:] {
expression = f.NewCommaExpression(expression, next)
}
return expression
}
//
// Utilities
//
func (f *NodeFactory) NewTypeCheck(value *ast.Node, tag string) *ast.Node {
if tag == "null" {
return f.NewStrictEqualityExpression(value, f.NewKeywordExpression(ast.KindNullKeyword))
} else if tag == "undefined" {
return f.NewStrictEqualityExpression(value, f.NewVoidZeroExpression())
} else {
return f.NewStrictEqualityExpression(f.NewTypeOfExpression(value), f.NewStringLiteral(tag))
}
}
func (f *NodeFactory) NewMethodCall(object *ast.Node, methodName *ast.Node, argumentsList []*ast.Node) *ast.Node {
// Preserve the optionality of `object`.
if ast.IsCallExpression(object) && (object.Flags&ast.NodeFlagsOptionalChain != 0) {
return f.NewCallExpression(
f.NewPropertyAccessExpression(object, nil, methodName, ast.NodeFlagsNone),
nil,
nil,
f.NewNodeList(argumentsList),
ast.NodeFlagsOptionalChain,
)
}
return f.NewCallExpression(
f.NewPropertyAccessExpression(object, nil, methodName, ast.NodeFlagsNone),
nil,
nil,
f.NewNodeList(argumentsList),
ast.NodeFlagsNone,
)
}
func (f *NodeFactory) NewGlobalMethodCall(globalObjectName string, methodName string, argumentsList []*ast.Node) *ast.Node {
return f.NewMethodCall(f.NewIdentifier(globalObjectName), f.NewIdentifier(methodName), argumentsList)
}
func (f *NodeFactory) NewFunctionCallCall(target *ast.Expression, thisArg *ast.Expression, argumentsList []*ast.Node) *ast.Node {
if thisArg == nil {
panic("Attempted to construct function call call without this argument expression")
}
args := append([]*ast.Expression{thisArg}, argumentsList...)
return f.NewMethodCall(target, f.NewIdentifier("call"), args)
}
func (f *NodeFactory) NewArraySliceCall(array *ast.Expression, start int) *ast.Node {
var args []*ast.Node
if start != 0 {
args = append(args, f.NewNumericLiteral(strconv.Itoa(start)))
}
return f.NewMethodCall(array, f.NewIdentifier("slice"), args)
}
// Determines whether a node is a parenthesized expression that can be ignored when recreating outer expressions.
//
// A parenthesized expression can be ignored when all of the following are true:
//
// - It's `pos` and `end` are not -1
// - It does not have a custom source map range
// - It does not have a custom comment range
// - It does not have synthetic leading or trailing comments
//
// If an outermost parenthesized expression is ignored, but the containing expression requires a parentheses around
// the expression to maintain precedence, a new parenthesized expression should be created automatically when
// the containing expression is created/updated.
func (f *NodeFactory) isIgnorableParen(node *ast.Expression) bool {
return ast.IsParenthesizedExpression(node) &&
ast.NodeIsSynthesized(node) &&
ast.RangeIsSynthesized(f.emitContext.SourceMapRange(node)) &&
ast.RangeIsSynthesized(f.emitContext.CommentRange(node)) // &&
// len(emitContext.SyntheticLeadingComments(node)) == 0 &&
// len(emitContext.SyntheticTrailingComments(node)) == 0
}
func (f *NodeFactory) updateOuterExpression(outerExpression *ast.Expression /*OuterExpression*/, expression *ast.Expression) *ast.Expression {
switch outerExpression.Kind {
case ast.KindParenthesizedExpression:
return f.UpdateParenthesizedExpression(outerExpression.AsParenthesizedExpression(), expression)
case ast.KindTypeAssertionExpression:
return f.UpdateTypeAssertion(outerExpression.AsTypeAssertion(), outerExpression.Type(), expression)
case ast.KindAsExpression:
return f.UpdateAsExpression(outerExpression.AsAsExpression(), expression, outerExpression.Type())
case ast.KindSatisfiesExpression:
return f.UpdateSatisfiesExpression(outerExpression.AsSatisfiesExpression(), expression, outerExpression.Type())
case ast.KindNonNullExpression:
return f.UpdateNonNullExpression(outerExpression.AsNonNullExpression(), expression)
case ast.KindExpressionWithTypeArguments:
return f.UpdateExpressionWithTypeArguments(outerExpression.AsExpressionWithTypeArguments(), expression, outerExpression.TypeArgumentList())
case ast.KindPartiallyEmittedExpression:
return f.UpdatePartiallyEmittedExpression(outerExpression.AsPartiallyEmittedExpression(), expression)
default:
panic(fmt.Sprintf("Unexpected outer expression kind: %s", outerExpression.Kind))
}
}
func (f *NodeFactory) RestoreOuterExpressions(outerExpression *ast.Expression, innerExpression *ast.Expression, kinds ast.OuterExpressionKinds) *ast.Expression {
if outerExpression != nil && ast.IsOuterExpression(outerExpression, kinds) && !f.isIgnorableParen(outerExpression) {
return f.updateOuterExpression(
outerExpression,
f.RestoreOuterExpressions(outerExpression.Expression(), innerExpression, ast.OEKAll),
)
}
return innerExpression
}
// Ensures `"use strict"` is the first statement of a slice of statements.
func (f *NodeFactory) EnsureUseStrict(statements []*ast.Statement) []*ast.Statement {
for _, statement := range statements {
if ast.IsPrologueDirective(statement) && statement.AsExpressionStatement().Expression.Text() == "use strict" {
return statements
} else {
break
}
}
useStrictPrologue := f.NewExpressionStatement(f.NewStringLiteral("use strict"))
statements = append([]*ast.Statement{useStrictPrologue}, statements...)
return statements
}
// Splits a slice of statements into two parts: standard prologue statements and the rest of the statements
func (f *NodeFactory) SplitStandardPrologue(source []*ast.Statement) (prologue []*ast.Statement, rest []*ast.Statement) {
for i, statement := range source {
if !ast.IsPrologueDirective(statement) {
return source[:i], source[i:]
}
}
return nil, source
}
// Splits a slice of statements into two parts: custom prologue statements (e.g., with `EFCustomPrologue` set) and the rest of the statements
func (f *NodeFactory) SplitCustomPrologue(source []*ast.Statement) (prologue []*ast.Statement, rest []*ast.Statement) {
for i, statement := range source {
if ast.IsPrologueDirective(statement) || f.emitContext.EmitFlags(statement)&EFCustomPrologue == 0 {
return source[:i], source[i:]
}
}
return nil, source
}
//
// Declaration Names
//
type NameOptions struct {
AllowComments bool // indicates whether comments may be emitted for the name.
AllowSourceMaps bool // indicates whether source maps may be emitted for the name.
}
type AssignedNameOptions struct {
AllowComments bool // indicates whether comments may be emitted for the name.
AllowSourceMaps bool // indicates whether source maps may be emitted for the name.
IgnoreAssignedName bool // indicates whether the assigned name of a declaration shouldn't be considered.
}
func (f *NodeFactory) getName(node *ast.Declaration, emitFlags EmitFlags, opts AssignedNameOptions) *ast.IdentifierNode {
var nodeName *ast.IdentifierNode
if node != nil {
if opts.IgnoreAssignedName {
nodeName = ast.GetNonAssignedNameOfDeclaration(node)
} else {
nodeName = ast.GetNameOfDeclaration(node)
}
}
if nodeName != nil {
name := nodeName.Clone(f)
if !opts.AllowComments {
emitFlags |= EFNoComments
}
if !opts.AllowSourceMaps {
emitFlags |= EFNoSourceMap
}
f.emitContext.AddEmitFlags(name, emitFlags)
return name
}
return f.NewGeneratedNameForNode(node)
}
// Gets the local name of a declaration. This is primarily used for declarations that can be referred to by name in the
// declaration's immediate scope (classes, enums, namespaces). A local name will *never* be prefixed with a module or
// namespace export modifier like "exports." when emitted as an expression.
func (f *NodeFactory) GetLocalName(node *ast.Declaration) *ast.IdentifierNode {
return f.GetLocalNameEx(node, AssignedNameOptions{})
}
// Gets the local name of a declaration. This is primarily used for declarations that can be referred to by name in the
// declaration's immediate scope (classes, enums, namespaces). A local name will *never* be prefixed with a module or
// namespace export modifier like "exports." when emitted as an expression.
func (f *NodeFactory) GetLocalNameEx(node *ast.Declaration, opts AssignedNameOptions) *ast.IdentifierNode {
return f.getName(node, EFLocalName, opts)
}
// Gets the export name of a declaration. This is primarily used for declarations that can be
// referred to by name in the declaration's immediate scope (classes, enums, namespaces). An
// export name will *always* be prefixed with an module or namespace export modifier like
// `"exports."` when emitted as an expression if the name points to an exported symbol.
func (f *NodeFactory) GetExportName(node *ast.Declaration) *ast.IdentifierNode {
return f.GetExportNameEx(node, AssignedNameOptions{})
}
// Gets the export name of a declaration. This is primarily used for declarations that can be
// referred to by name in the declaration's immediate scope (classes, enums, namespaces). An
// export name will *always* be prefixed with an module or namespace export modifier like
// `"exports."` when emitted as an expression if the name points to an exported symbol.
func (f *NodeFactory) GetExportNameEx(node *ast.Declaration, opts AssignedNameOptions) *ast.IdentifierNode {
return f.getName(node, EFExportName, opts)
}
// Gets the name of a declaration to use during emit.
func (f *NodeFactory) GetDeclarationName(node *ast.Declaration) *ast.IdentifierNode {
return f.GetDeclarationNameEx(node, NameOptions{})
}
// Gets the name of a declaration to use during emit.
func (f *NodeFactory) GetDeclarationNameEx(node *ast.Declaration, opts NameOptions) *ast.IdentifierNode {
return f.getName(node, EFNone, AssignedNameOptions{AllowComments: opts.AllowComments, AllowSourceMaps: opts.AllowSourceMaps})
}
func (f *NodeFactory) GetNamespaceMemberName(ns *ast.IdentifierNode, name *ast.IdentifierNode, opts NameOptions) *ast.IdentifierNode {
if !f.emitContext.HasAutoGenerateInfo(name) {
name = name.Clone(f)
}
qualifiedName := f.NewPropertyAccessExpression(ns, nil /*questionDotToken*/, name, ast.NodeFlagsNone)
f.emitContext.AssignCommentAndSourceMapRanges(qualifiedName, name)
if !opts.AllowComments {
f.emitContext.AddEmitFlags(qualifiedName, EFNoComments)
}
if !opts.AllowSourceMaps {
f.emitContext.AddEmitFlags(qualifiedName, EFNoSourceMap)
}
return qualifiedName
}
//
// Emit Helpers
//
// Allocates a new Identifier representing a reference to a helper function.
func (f *NodeFactory) NewUnscopedHelperName(name string) *ast.IdentifierNode {
node := f.NewIdentifier(name)
f.emitContext.SetEmitFlags(node, EFHelperName)
return node
}
// !!! TypeScript Helpers
// ESNext Helpers
func (f *NodeFactory) NewAddDisposableResourceHelper(envBinding *ast.Expression, value *ast.Expression, async bool) *ast.Expression {
f.emitContext.RequestEmitHelper(addDisposableResourceHelper)
return f.NewCallExpression(
f.NewUnscopedHelperName("__addDisposableResource"),
nil, /*questionDotToken*/
nil, /*typeArguments*/
f.NewNodeList([]*ast.Expression{envBinding, value, f.NewKeywordExpression(core.IfElse(async, ast.KindTrueKeyword, ast.KindFalseKeyword))}),
ast.NodeFlagsNone,
)
}
func (f *NodeFactory) NewDisposeResourcesHelper(envBinding *ast.Expression) *ast.Expression {
f.emitContext.RequestEmitHelper(disposeResourcesHelper)
return f.NewCallExpression(
f.NewUnscopedHelperName("__disposeResources"),
nil, /*questionDotToken*/
nil, /*typeArguments*/
f.NewNodeList([]*ast.Expression{envBinding}),
ast.NodeFlagsNone,
)
}
// !!! Class Fields Helpers
// !!! ES2018 Helpers
// Chains a sequence of expressions using the __assign helper or Object.assign if available in the target
func (f *NodeFactory) NewAssignHelper(attributesSegments []*ast.Expression, scriptTarget core.ScriptTarget) *ast.Expression {
if scriptTarget >= core.ScriptTargetES2015 {
return f.NewCallExpression(f.NewPropertyAccessExpression(f.NewIdentifier("Object"), nil, f.NewIdentifier("assign"), ast.NodeFlagsNone), nil, nil, f.NewNodeList(attributesSegments), ast.NodeFlagsNone)
}
f.emitContext.RequestEmitHelper(assignHelper)
return f.NewCallExpression(
f.NewUnscopedHelperName("__assign"),
nil,
nil,
f.NewNodeList(attributesSegments),
ast.NodeFlagsNone,
)
}
// ES2018 Destructuring Helpers
func (f *NodeFactory) NewRestHelper(value *ast.Expression, elements []*ast.Node, computedTempVariables []*ast.Node, location core.TextRange) *ast.Expression {
f.emitContext.RequestEmitHelper(restHelper)
var propertyNames []*ast.Node
computedTempVariableOffset := 0
for i, element := range elements {
if i == len(elements)-1 {
break
}
propertyName := ast.TryGetPropertyNameOfBindingOrAssignmentElement(element)
if propertyName != nil {
if ast.IsComputedPropertyName(propertyName) {
debug.AssertIsDefined(computedTempVariables, "Encountered computed property name but 'computedTempVariables' argument was not provided.")
temp := computedTempVariables[computedTempVariableOffset]
computedTempVariableOffset++
// typeof _tmp === "symbol" ? _tmp : _tmp + ""
propertyNames = append(propertyNames, f.NewConditionalExpression(
f.NewTypeCheck(temp, "symbol"),
f.NewToken(ast.KindQuestionToken),
temp,
f.NewToken(ast.KindColonToken),
f.NewBinaryExpression(nil, temp, nil, f.NewToken(ast.KindPlusToken), f.NewStringLiteral("")),
))
} else {
propertyNames = append(propertyNames, f.NewStringLiteralFromNode(propertyName))
}
}
}
propNames := f.NewArrayLiteralExpression(f.NewNodeList(propertyNames), false)
propNames.Loc = location
return f.NewCallExpression(
f.NewUnscopedHelperName("__rest"),
nil,
nil,
f.NewNodeList([]*ast.Node{
value,
propNames,
}),
ast.NodeFlagsNone,
)
}
// !!! ES2017 Helpers
// ES2015 Helpers
func (f *NodeFactory) NewPropKeyHelper(expr *ast.Expression) *ast.Expression {
f.emitContext.RequestEmitHelper(propKeyHelper)
return f.NewCallExpression(
f.NewUnscopedHelperName("__propKey"),
nil, /*questionDotToken*/
nil, /*typeArguments*/
f.NewNodeList([]*ast.Expression{expr}),
ast.NodeFlagsNone,
)
}
func (f *NodeFactory) NewSetFunctionNameHelper(fn *ast.Expression, name *ast.Expression, prefix string) *ast.Expression {
f.emitContext.RequestEmitHelper(setFunctionNameHelper)
var arguments []*ast.Expression
if len(prefix) > 0 {
arguments = []*ast.Expression{fn, name, f.NewStringLiteral(prefix)}
} else {
arguments = []*ast.Expression{fn, name}
}
return f.NewCallExpression(
f.NewUnscopedHelperName("__setFunctionName"),
nil, /*questionDotToken*/
nil, /*typeArguments*/
f.NewNodeList(arguments),
ast.NodeFlagsNone,
)
}
// ES Module Helpers
// Allocates a new Call expression to the `__importDefault` helper.
func (f *NodeFactory) NewImportDefaultHelper(expression *ast.Expression) *ast.Expression {
f.emitContext.RequestEmitHelper(importDefaultHelper)
return f.NewCallExpression(
f.NewUnscopedHelperName("__importDefault"),
nil, /*questionDotToken*/
nil, /*typeArguments*/
f.NewNodeList([]*ast.Expression{expression}),
ast.NodeFlagsNone,
)
}
// Allocates a new Call expression to the `__importStar` helper.
func (f *NodeFactory) NewImportStarHelper(expression *ast.Expression) *ast.Expression {
f.emitContext.RequestEmitHelper(importStarHelper)
return f.NewCallExpression(
f.NewUnscopedHelperName("__importStar"),
nil, /*questionDotToken*/
nil, /*typeArguments*/
f.NewNodeList([]*ast.Expression{expression}),
ast.NodeFlagsNone,
)
}
// Allocates a new Call expression to the `__exportStar` helper.
func (f *NodeFactory) NewExportStarHelper(moduleExpression *ast.Expression, exportsExpression *ast.Expression) *ast.Expression {
f.emitContext.RequestEmitHelper(exportStarHelper)
return f.NewCallExpression(
f.NewUnscopedHelperName("__exportStar"),
nil, /*questionDotToken*/
nil, /*typeArguments*/
f.NewNodeList([]*ast.Expression{moduleExpression, exportsExpression}),
ast.NodeFlagsNone,
)
}
// Allocates a new Call expression to the `__rewriteRelativeImportExtension` helper.
func (f *NodeFactory) NewRewriteRelativeImportExtensionsHelper(firstArgument *ast.Node, preserveJsx bool) *ast.Expression {
f.emitContext.RequestEmitHelper(rewriteRelativeImportExtensionsHelper)
var arguments []*ast.Expression
if preserveJsx {
arguments = []*ast.Expression{firstArgument, f.NewToken(ast.KindTrueKeyword)}
} else {
arguments = []*ast.Expression{firstArgument}
}
return f.NewCallExpression(
f.NewUnscopedHelperName("__rewriteRelativeImportExtension"),
nil, /*questionDotToken*/
nil, /*typeArguments*/
f.NewNodeList(arguments),
ast.NodeFlagsNone,
)
}

View File

@ -1,57 +0,0 @@
package printer
type GeneratedIdentifierFlags int
const (
// Kind
GeneratedIdentifierFlagsNone = iota // Not automatically generated.
GeneratedIdentifierFlagsAuto // Automatically generated identifier.
GeneratedIdentifierFlagsLoop // Automatically generated identifier with a preference for '_i'.
GeneratedIdentifierFlagsUnique // Unique name based on the 'text' property.
GeneratedIdentifierFlagsNode // Unique name based on the node in the 'Node' property.
GeneratedIdentifierFlagsKindMask = 7 // Mask to extract the kind of identifier from its flags.
// Flags
GeneratedIdentifierFlagsReservedInNestedScopes = 1 << 3 // Reserve the generated name in nested scopes
GeneratedIdentifierFlagsOptimistic = 1 << 4 // First instance won't use '_#' if there's no conflict
GeneratedIdentifierFlagsFileLevel = 1 << 5 // Use only the file identifiers list and not generated names to search for conflicts
GeneratedIdentifierFlagsAllowNameSubstitution = 1 << 6 // Used by `module.ts` to indicate generated nodes which can have substitutions performed upon them (as they were generated by an earlier transform phase)
)
func (f GeneratedIdentifierFlags) Kind() GeneratedIdentifierFlags {
return f & GeneratedIdentifierFlagsKindMask
}
func (f GeneratedIdentifierFlags) IsAuto() bool {
return f.Kind() == GeneratedIdentifierFlagsAuto
}
func (f GeneratedIdentifierFlags) IsLoop() bool {
return f.Kind() == GeneratedIdentifierFlagsLoop
}
func (f GeneratedIdentifierFlags) IsUnique() bool {
return f.Kind() == GeneratedIdentifierFlagsUnique
}
func (f GeneratedIdentifierFlags) IsNode() bool {
return f.Kind() == GeneratedIdentifierFlagsNode
}
func (f GeneratedIdentifierFlags) IsReservedInNestedScopes() bool {
return f&GeneratedIdentifierFlagsReservedInNestedScopes != 0
}
func (f GeneratedIdentifierFlags) IsOptimistic() bool {
return f&GeneratedIdentifierFlagsOptimistic != 0
}
func (f GeneratedIdentifierFlags) IsFileLevel() bool {
return f&GeneratedIdentifierFlagsFileLevel != 0
}
func (f GeneratedIdentifierFlags) HasAllowNameSubstitution() bool {
return f&GeneratedIdentifierFlagsAllowNameSubstitution != 0
}

View File

@ -1,252 +0,0 @@
package printer
type Priority struct {
Value int
}
type EmitHelper struct {
Name string // A unique name for this helper.
Scoped bool // Indicates whether the helper MUST be emitted in the current scope.
Text string // ES3-compatible raw script text
TextCallback func(makeUniqueName func(string) string) string // A function yielding an ES3-compatible raw script text.
Priority *Priority // Helpers with a higher priority are emitted earlier than other helpers on the node.
Dependencies []*EmitHelper // Emit helpers this helper depends on
ImportName string // The name of the helper to use when importing via `--importHelpers`.
}
func compareEmitHelpers(x *EmitHelper, y *EmitHelper) int {
if x == y {
return 0
}
if x.Priority == y.Priority {
return 0
}
if x.Priority == nil {
return 1
}
if y.Priority == nil {
return -1
}
return x.Priority.Value - y.Priority.Value
}
// !!! TypeScript Helpers
// ESNext Helpers
var addDisposableResourceHelper = &EmitHelper{
Name: "typescript:addDisposableResource",
ImportName: "__addDisposableResource",
Scoped: false,
Text: `var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
if (value !== null && value !== void 0) {
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
var dispose, inner;
if (async) {
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
dispose = value[Symbol.asyncDispose];
}
if (dispose === void 0) {
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
dispose = value[Symbol.dispose];
if (async) inner = dispose;
}
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
env.stack.push({ value: value, dispose: dispose, async: async });
}
else if (async) {
env.stack.push({ async: true });
}
return value;
};`,
}
var disposeResourcesHelper = &EmitHelper{
Name: "typescript:disposeResources",
ImportName: "__disposeResources",
Scoped: false,
Text: `var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
return function (env) {
function fail(e) {
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
env.hasError = true;
}
var r, s = 0;
function next() {
while (r = env.stack.pop()) {
try {
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
if (r.dispose) {
var result = r.dispose.call(r.value);
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
}
else s |= 1;
}
catch (e) {
fail(e);
}
}
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
if (env.hasError) throw env.error;
}
return next();
};
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
});`,
}
// !!! Class Fields Helpers
// !!! ES2018 Helpers
var assignHelper = &EmitHelper{
Name: "typescript:assign",
ImportName: "__assign",
Scoped: false,
Priority: &Priority{1},
Text: `var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};`,
}
// !!! ES2018 Destructuring Helpers
var restHelper = &EmitHelper{
Name: "typescript:rest",
ImportName: "__rest",
Scoped: false,
Text: `var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};`,
}
// !!! ES2017 Helpers
// ES2015 Helpers
var propKeyHelper = &EmitHelper{
Name: "typescript:propKey",
ImportName: "__propKey",
Scoped: false,
Text: `var __propKey = (this && this.__propKey) || function (x) {
return typeof x === "symbol" ? x : "".concat(x);
};`,
}
// https://tc39.es/ecma262/#sec-setfunctionname
var setFunctionNameHelper = &EmitHelper{
Name: "typescript:setFunctionName",
ImportName: "__setFunctionName",
Scoped: false,
Text: `var __setFunctionName = (this && this.__setFunctionName) || function (f, name, prefix) {
if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : "";
return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name });
};`,
}
// ES Module Helpers
var createBindingHelper = &EmitHelper{
Name: "typescript:commonjscreatebinding",
ImportName: "__createBinding",
Scoped: false,
Priority: &Priority{1},
Text: `var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));`,
}
var setModuleDefaultHelper = &EmitHelper{
Name: "typescript:commonjscreatevalue",
ImportName: "__setModuleDefault",
Scoped: false,
Priority: &Priority{1},
Text: `var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});`,
}
var importStarHelper = &EmitHelper{
Name: "typescript:commonjsimportstar",
ImportName: "__importStar",
Scoped: false,
Dependencies: []*EmitHelper{createBindingHelper, setModuleDefaultHelper},
Priority: &Priority{2},
Text: `var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();`,
}
var importDefaultHelper = &EmitHelper{
Name: "typescript:commonjsimportdefault",
ImportName: "__importDefault",
Scoped: false,
Text: `var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};`,
}
var exportStarHelper = &EmitHelper{
Name: "typescript:export-star",
ImportName: "__exportStar",
Scoped: false,
Dependencies: []*EmitHelper{createBindingHelper},
Priority: &Priority{2},
Text: `var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};`,
}
var rewriteRelativeImportExtensionsHelper = &EmitHelper{
Name: "typescript:rewriteRelativeImportExtensions",
ImportName: "__rewriteRelativeImportExtension",
Scoped: false,
Text: `var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
if (typeof path === "string" && /^\.\.?\//.test(path)) {
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
});
}
return path;
};`,
}

View File

@ -1,401 +0,0 @@
package printer
import (
"fmt"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/collections"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/debug"
)
// Flags enum to track count of temp variables and a few dedicated names
type tempFlags int
const (
tempFlagsAuto tempFlags = 0x00000000 // No preferred name
tempFlagsCountMask tempFlags = 0x0FFFFFFF // Temp variable counter
tempFlags_i tempFlags = 0x10000000 // Use/preference flag for '_i'
)
type NameGenerator struct {
Context *EmitContext
IsFileLevelUniqueNameInCurrentFile func(string, bool) bool // callback for Printer.isFileLevelUniqueNameInCurrentFile
GetTextOfNode func(*ast.Node) string // callback for Printer.getTextOfNode
nodeIdToGeneratedName map[ast.NodeId]string // Map of generated names for specific nodes
nodeIdToGeneratedPrivateName map[ast.NodeId]string // Map of generated private names for specific nodes
autoGeneratedIdToGeneratedName map[AutoGenerateId]string // Map of generated names for temp and loop variables
nameGenerationScope *nameGenerationScope
privateNameGenerationScope *nameGenerationScope
generatedNames collections.Set[string] // NOTE: Used to match Strada, but should be moved to nameGenerationScope after port is complete.
}
type nameGenerationScope struct {
next *nameGenerationScope // The next nameGenerationScope in the stack
tempFlags tempFlags // TempFlags for the current name generation scope.
formattedNameTempFlags map[string]tempFlags // TempFlags for the current name generation scope.
reservedNames collections.Set[string] // Names reserved in nested name generation scopes.
// generatedNames collections.Set[string] // NOTE: generated names should be scoped after Strada port is complete.
}
func (g *NameGenerator) PushScope(reuseTempVariableScope bool) {
g.privateNameGenerationScope = &nameGenerationScope{next: g.privateNameGenerationScope}
if !reuseTempVariableScope {
g.nameGenerationScope = &nameGenerationScope{next: g.nameGenerationScope}
}
}
func (g *NameGenerator) PopScope(reuseTempVariableScope bool) {
if g.privateNameGenerationScope != nil {
g.privateNameGenerationScope = g.privateNameGenerationScope.next
}
if !reuseTempVariableScope && g.nameGenerationScope != nil {
g.nameGenerationScope = g.nameGenerationScope.next
}
}
func (g *NameGenerator) getScope(privateName bool) **nameGenerationScope {
return core.IfElse(privateName, &g.privateNameGenerationScope, &g.nameGenerationScope)
}
func (g *NameGenerator) getTempFlags(privateName bool) tempFlags {
scope := g.getScope(privateName)
if *scope != nil {
return (*scope).tempFlags
}
return tempFlagsAuto
}
func (g *NameGenerator) setTempFlags(privateName bool, flags tempFlags) {
scope := g.getScope(privateName)
if *scope == nil {
*scope = &nameGenerationScope{}
}
(*scope).tempFlags = flags
}
// Gets the TempFlags to use in the current nameGenerationScope for the given key
func (g *NameGenerator) getTempFlagsForFormattedName(privateName bool, formattedNameKey string) tempFlags {
scope := g.getScope(privateName)
if *scope != nil {
if flags, ok := (*scope).formattedNameTempFlags[formattedNameKey]; ok {
return flags
}
}
return tempFlagsAuto
}
// Sets the TempFlags to use in the current nameGenerationScope for the given key
func (g *NameGenerator) setTempFlagsForFormattedName(privateName bool, formattedNameKey string, flags tempFlags) {
scope := g.getScope(privateName)
if *scope == nil {
*scope = &nameGenerationScope{}
}
if (*scope).formattedNameTempFlags == nil {
(*scope).formattedNameTempFlags = make(map[string]tempFlags)
}
(*scope).formattedNameTempFlags[formattedNameKey] = flags
}
func (g *NameGenerator) reserveName(name string, privateName bool, scoped bool, temp bool) {
scope := g.getScope(privateName)
if *scope == nil {
*scope = &nameGenerationScope{}
}
if privateName || scoped {
(*scope).reservedNames.Add(name)
} else if !temp {
g.generatedNames.Add(name) // NOTE: Matches Strada, but is incorrect.
// (*scope).generatedNames.Add(name) // TODO: generated names should be scoped after Strada port is complete.
}
}
// Generate the text for a generated identifier or private identifier
func (g *NameGenerator) GenerateName(name *ast.MemberName) string {
if g.Context != nil {
if autoGenerate, ok := g.Context.autoGenerate[name]; ok {
if autoGenerate.Flags.IsNode() {
// Node names generate unique names based on their original node
// and are cached based on that node's id.
return g.generateNameForNodeCached(g.Context.GetNodeForGeneratedName(name), ast.IsPrivateIdentifier(name), autoGenerate.Flags, autoGenerate.Prefix, autoGenerate.Suffix)
} else {
// Auto, Loop, and Unique names are cached based on their unique autoGenerateId.
if autoGeneratedName, ok := g.autoGeneratedIdToGeneratedName[autoGenerate.Id]; ok {
return autoGeneratedName
}
if g.autoGeneratedIdToGeneratedName == nil {
g.autoGeneratedIdToGeneratedName = make(map[AutoGenerateId]string)
}
autoGeneratedName := g.makeName(name)
g.autoGeneratedIdToGeneratedName[autoGenerate.Id] = autoGeneratedName
return autoGeneratedName
}
}
}
return g.GetTextOfNode(name)
}
func (g *NameGenerator) generateNameForNodeCached(node *ast.Node, privateName bool, flags GeneratedIdentifierFlags, prefix string, suffix string) string {
nodeId := ast.GetNodeId(node)
cache := core.IfElse(privateName, &g.nodeIdToGeneratedPrivateName, &g.nodeIdToGeneratedName)
if *cache == nil {
*cache = make(map[ast.NodeId]string)
}
if name, ok := (*cache)[nodeId]; ok {
return name
}
name := g.generateNameForNode(node, privateName, flags, prefix, suffix)
(*cache)[nodeId] = name
return name
}
func (g *NameGenerator) generateNameForNode(node *ast.Node, privateName bool, flags GeneratedIdentifierFlags, prefix string, suffix string) string {
switch node.Kind {
case ast.KindIdentifier, ast.KindPrivateIdentifier:
return g.makeUniqueName(g.GetTextOfNode(node), nil /*checkFn*/, flags.IsOptimistic(), flags.IsReservedInNestedScopes(), privateName, prefix, suffix)
case ast.KindModuleDeclaration, ast.KindEnumDeclaration:
if privateName || len(prefix) > 0 || len(suffix) > 0 {
panic("Generated name for a module or enum cannot be private and may have neither a prefix nor suffix")
}
return g.generateNameForModuleOrEnum(node)
case ast.KindImportDeclaration, ast.KindJSImportDeclaration, ast.KindExportDeclaration:
if privateName || len(prefix) > 0 || len(suffix) > 0 {
panic("Generated name for an import or export cannot be private and may have neither a prefix nor suffix")
}
return g.generateNameForImportOrExportDeclaration(node)
case ast.KindFunctionDeclaration, ast.KindClassDeclaration:
if privateName || len(prefix) > 0 || len(suffix) > 0 {
panic("Generated name for a class or function declaration cannot be private and may have neither a prefix nor suffix")
}
name := node.Name()
if name != nil && !(g.Context == nil && g.Context.HasAutoGenerateInfo(name)) {
return g.generateNameForNode(name, false /*privateName*/, flags, "" /*prefix*/, "" /*suffix*/)
}
return g.generateNameForExportDefault()
case ast.KindExportAssignment:
if privateName || len(prefix) > 0 || len(suffix) > 0 {
panic("Generated name for an export assignment cannot be private and may have neither a prefix nor suffix")
}
return g.generateNameForExportDefault()
case ast.KindClassExpression:
if privateName || len(prefix) > 0 || len(suffix) > 0 {
panic("Generated name for a class expression cannot be private and may have neither a prefix nor suffix")
}
return g.generateNameForClassExpression()
case ast.KindMethodDeclaration, ast.KindGetAccessor, ast.KindSetAccessor:
return g.generateNameForMethodOrAccessor(node, privateName, prefix, suffix)
case ast.KindComputedPropertyName:
return g.makeTempVariableName(tempFlagsAuto, true /*reservedInNestedScopes*/, privateName, prefix, suffix)
default:
return g.makeTempVariableName(tempFlagsAuto, false /*reservedInNestedScopes*/, privateName, prefix, suffix)
}
}
func (g *NameGenerator) generateNameForModuleOrEnum(node *ast.Node /* ModuleDeclaration | EnumDeclaration */) string {
name := g.GetTextOfNode(node.Name())
// Use module/enum name itself if it is unique, otherwise make a unique variation
if isUniqueLocalName(name, node) {
return name
} else {
return g.makeUniqueName(name, nil /*checkFn*/, false /*optimistic*/, false /*scoped*/, false /*privateName*/, "" /*prefix*/, "" /*suffix*/)
}
}
func (g *NameGenerator) generateNameForImportOrExportDeclaration(node *ast.Node /* ImportDeclaration | ExportDeclaration */) string {
expr := ast.GetExternalModuleName(node)
baseName := "module"
if ast.IsStringLiteral(expr) {
baseName = makeIdentifierFromModuleName(expr.Text())
}
return g.makeUniqueName(baseName, nil /*checkFn*/, false /*optimistic*/, false /*scoped*/, false /*privateName*/, "" /*prefix*/, "" /*suffix*/)
}
func (g *NameGenerator) generateNameForExportDefault() string {
return g.makeUniqueName("default", nil /*checkFn*/, false /*optimistic*/, false /*scoped*/, false /*privateName*/, "" /*prefix*/, "" /*suffix*/)
}
func (g *NameGenerator) generateNameForClassExpression() string {
return g.makeUniqueName("class", nil /*checkFn*/, false /*optimistic*/, false /*scoped*/, false /*privateName*/, "" /*prefix*/, "" /*suffix*/)
}
func (g *NameGenerator) generateNameForMethodOrAccessor(node *ast.Node /* MethodDeclaration | AccessorDeclaration */, privateName bool, prefix string, suffix string) string {
if ast.IsIdentifier(node.Name()) {
return g.generateNameForNodeCached(node.Name(), privateName, GeneratedIdentifierFlagsNone, prefix, suffix)
}
return g.makeTempVariableName(tempFlagsAuto, false /*reservedInNestedScopes*/, privateName, prefix, suffix)
}
func (g *NameGenerator) makeName(name *ast.Node) string {
if g.Context != nil {
if autoGenerate, ok := g.Context.autoGenerate[name]; ok {
switch autoGenerate.Flags.Kind() {
case GeneratedIdentifierFlagsAuto:
return g.makeTempVariableName(tempFlagsAuto, autoGenerate.Flags.IsReservedInNestedScopes(), ast.IsPrivateIdentifier(name), autoGenerate.Prefix, autoGenerate.Suffix)
case GeneratedIdentifierFlagsLoop:
debug.AssertNode(name, ast.IsIdentifier)
return g.makeTempVariableName(tempFlags_i, autoGenerate.Flags.IsReservedInNestedScopes(), false /*privateName*/, autoGenerate.Prefix, autoGenerate.Suffix)
case GeneratedIdentifierFlagsUnique:
return g.makeUniqueName(
name.Text(),
core.IfElse(autoGenerate.Flags.IsFileLevel(), g.IsFileLevelUniqueNameInCurrentFile, nil),
autoGenerate.Flags.IsOptimistic(),
autoGenerate.Flags.IsReservedInNestedScopes(),
ast.IsPrivateIdentifier(name),
autoGenerate.Prefix,
autoGenerate.Suffix,
)
}
}
}
return g.GetTextOfNode(name)
}
// Return the next available name in the pattern _a ... _z, _0, _1, ...
// TempFlags._i may be used to express a preference for that dedicated name.
// Note that names generated by makeTempVariableName and makeUniqueName will never conflict.
func (g *NameGenerator) makeTempVariableName(flags tempFlags, reservedInNestedScopes bool, privateName bool, prefix string, suffix string) string {
var tempFlags tempFlags
var key string
simple := len(prefix) == 0 && len(suffix) == 0
if simple {
tempFlags = g.getTempFlags(privateName)
} else {
// Generate a key to use to acquire a TempFlags counter based on the fixed portions of the generated name.
key = FormatGeneratedName(privateName, prefix, "" /*base*/, suffix)
if privateName {
key = ensureLeadingHash(key)
}
tempFlags = g.getTempFlagsForFormattedName(privateName, key)
}
if flags != 0 && tempFlags&flags == 0 {
fullName := FormatGeneratedName(privateName, prefix, "_i", suffix)
if g.isUniqueName(fullName, privateName) {
tempFlags |= flags
g.reserveName(fullName, privateName, reservedInNestedScopes, true /*temp*/)
if simple {
g.setTempFlags(privateName, tempFlags)
} else {
g.setTempFlagsForFormattedName(privateName, key, tempFlags)
}
return fullName
}
}
for {
count := tempFlags & tempFlagsCountMask
tempFlags++
// Skip over 'i' and 'n'
if count != 8 && count != 13 {
var name string
if count < 26 {
name = fmt.Sprintf("_%c", 'a'+byte(count))
} else {
name = fmt.Sprintf("_%d", count-26)
}
fullName := FormatGeneratedName(privateName, prefix, name, suffix)
if g.isUniqueName(fullName, privateName) {
g.reserveName(fullName, privateName, reservedInNestedScopes, true /*temp*/)
if simple {
g.setTempFlags(privateName, tempFlags)
} else {
g.setTempFlagsForFormattedName(privateName, key, tempFlags)
}
return fullName
}
}
}
}
// Generate a name that is unique within the current file and doesn't conflict with any names
// in global scope. The name is formed by adding an '_n' suffix to the specified base name,
// where n is a positive integer. Note that names generated by makeTempVariableName and
// makeUniqueName are guaranteed to never conflict.
// If `optimistic` is set, the first instance will use 'baseName' verbatim instead of 'baseName_1'
func (g *NameGenerator) makeUniqueName(baseName string, checkFn func(name string, privateName bool) bool, optimistic bool, scoped bool, privateName bool, prefix string, suffix string) string {
baseName = removeLeadingHash(baseName)
if optimistic {
fullName := FormatGeneratedName(privateName, prefix, baseName, suffix)
if g.checkUniqueName(fullName, privateName, checkFn) {
g.reserveName(fullName, privateName, scoped, false /*temp*/)
return fullName
}
}
// Find the first unique 'name_n', where n is a positive integer
if len(baseName) > 0 && baseName[len(baseName)-1] != '_' {
baseName += "_"
}
i := 1
for {
fullName := FormatGeneratedName(privateName, prefix, fmt.Sprintf("%s%d", baseName, i), suffix)
if g.checkUniqueName(fullName, privateName, checkFn) {
g.reserveName(fullName, privateName, scoped, false /*temp*/)
return fullName
}
i++
}
}
func (g *NameGenerator) checkUniqueName(name string, privateName bool, checkFn func(name string, privateName bool) bool) bool {
if checkFn != nil {
return checkFn(name, privateName)
} else {
return g.isUniqueName(name, privateName)
}
}
func nextContainer(node *ast.Node) *ast.Node {
data := node.LocalsContainerData()
if data != nil {
return data.NextContainer
}
return nil
}
func isUniqueLocalName(name string, container *ast.Node) bool {
node := container
for node != nil && ast.IsNodeDescendantOf(node, container) && node.LocalsContainerData() != nil {
locals := node.Locals()
if locals != nil {
// We conservatively include alias symbols to cover cases where they're emitted as locals
if local, ok := locals[name]; ok && local.Flags&(ast.SymbolFlagsValue|ast.SymbolFlagsExportValue|ast.SymbolFlagsAlias) != 0 {
return false
}
}
node = nextContainer(node)
}
return true
}
func (g *NameGenerator) isUniqueName(name string, privateName bool) bool {
return (g.IsFileLevelUniqueNameInCurrentFile == nil || g.IsFileLevelUniqueNameInCurrentFile(name, privateName)) &&
!g.isReservedName(name, privateName)
}
func (g *NameGenerator) isReservedName(name string, privateName bool) bool {
scope := g.getScope(privateName)
// NOTE: The following matches Strada, but is incorrect.
if g.generatedNames.Has(name) {
return true
}
// TODO: generated names should be scoped after Strada port is complete.
////if *scope != nil {
//// if (*scope).generatedNames.Has(name) {
//// return true
//// }
////}
for *scope != nil {
if (*scope).reservedNames.Has(name) {
return true
}
scope = &(*scope).next
}
return false
}

View File

@ -1,640 +0,0 @@
package printer_test
import (
"testing"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/binder"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/printer"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/testutil/parsetestutil"
"gotest.tools/v3/assert"
)
func TestTempVariable1(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
name1 := ec.Factory.NewTempVariable()
name2 := ec.Factory.NewTempVariable()
g := &printer.NameGenerator{Context: ec}
text1 := g.GenerateName(name1)
text2 := g.GenerateName(name2)
assert.Equal(t, "_a", text1)
assert.Equal(t, "_b", text2)
}
func TestTempVariable2(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
name1 := ec.Factory.NewTempVariableEx(printer.AutoGenerateOptions{
Prefix: "A",
Suffix: "B",
})
name2 := ec.Factory.NewTempVariableEx(printer.AutoGenerateOptions{
Prefix: "A",
Suffix: "B",
})
g := &printer.NameGenerator{Context: ec}
text1 := g.GenerateName(name1)
text2 := g.GenerateName(name2)
assert.Equal(t, "A_aB", text1)
assert.Equal(t, "A_bB", text2)
}
func TestTempVariable3(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
name1 := ec.Factory.NewTempVariable()
g := &printer.NameGenerator{Context: ec}
text1 := g.GenerateName(name1)
text2 := g.GenerateName(name1)
assert.Equal(t, "_a", text1)
assert.Equal(t, "_a", text2)
}
func TestTempVariableScoped(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
name1 := ec.Factory.NewTempVariable()
name2 := ec.Factory.NewTempVariable()
g := &printer.NameGenerator{Context: ec}
text1 := g.GenerateName(name1)
g.PushScope(false)
text2 := g.GenerateName(name2)
g.PopScope(false)
assert.Equal(t, "_a", text1)
assert.Equal(t, "_a", text2)
}
func TestTempVariableScopedReserved(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
name1 := ec.Factory.NewTempVariableEx(printer.AutoGenerateOptions{Flags: printer.GeneratedIdentifierFlagsReservedInNestedScopes})
name2 := ec.Factory.NewTempVariable()
g := &printer.NameGenerator{Context: ec}
text1 := g.GenerateName(name1)
g.PushScope(false)
text2 := g.GenerateName(name2)
g.PopScope(false)
assert.Equal(t, "_a", text1)
assert.Equal(t, "_b", text2)
}
func TestLoopVariable1(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
name1 := ec.Factory.NewLoopVariable()
name2 := ec.Factory.NewLoopVariable()
g := &printer.NameGenerator{Context: ec}
text1 := g.GenerateName(name1)
text2 := g.GenerateName(name2)
assert.Equal(t, "_i", text1)
assert.Equal(t, "_a", text2)
}
func TestLoopVariable2(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
name1 := ec.Factory.NewLoopVariableEx(printer.AutoGenerateOptions{
Prefix: "A",
Suffix: "B",
})
name2 := ec.Factory.NewLoopVariableEx(printer.AutoGenerateOptions{
Prefix: "A",
Suffix: "B",
})
g := &printer.NameGenerator{Context: ec}
text1 := g.GenerateName(name1)
text2 := g.GenerateName(name2)
assert.Equal(t, "A_iB", text1)
assert.Equal(t, "A_aB", text2)
}
func TestLoopVariable3(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
name1 := ec.Factory.NewLoopVariable()
g := &printer.NameGenerator{Context: ec}
text1 := g.GenerateName(name1)
text2 := g.GenerateName(name1)
assert.Equal(t, "_i", text1)
assert.Equal(t, "_i", text2)
}
func TestLoopVariableScoped(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
name1 := ec.Factory.NewLoopVariable()
name2 := ec.Factory.NewLoopVariable()
g := &printer.NameGenerator{Context: ec}
text1 := g.GenerateName(name1)
g.PushScope(false)
text2 := g.GenerateName(name2)
g.PopScope(false)
assert.Equal(t, "_i", text1)
assert.Equal(t, "_i", text2)
}
func TestUniqueName1(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
name1 := ec.Factory.NewUniqueName("foo")
name2 := ec.Factory.NewUniqueName("foo")
g := &printer.NameGenerator{Context: ec}
text1 := g.GenerateName(name1)
text2 := g.GenerateName(name2)
assert.Equal(t, "foo_1", text1)
assert.Equal(t, "foo_2", text2)
}
func TestUniqueName2(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
name1 := ec.Factory.NewUniqueName("foo")
g := &printer.NameGenerator{Context: ec}
text1 := g.GenerateName(name1)
text2 := g.GenerateName(name1)
assert.Equal(t, "foo_1", text1)
// Expected to be same because GenerateName goes off object identity
assert.Equal(t, "foo_1", text2)
}
func TestUniqueNameScoped(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
name1 := ec.Factory.NewUniqueName("foo")
name2 := ec.Factory.NewUniqueName("foo")
g := &printer.NameGenerator{Context: ec}
assert.Equal(t, "foo_1", g.GenerateName(name1))
g.PushScope(false)
assert.Equal(t, "foo_2", g.GenerateName(name2)) // Matches Strada, but is incorrect
// assert.Equal(t, "foo_1", g.GenerateName(name2)) // TODO: Fix after Strada port is complete.
g.PopScope(false)
}
func TestUniquePrivateName1(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
name1 := ec.Factory.NewUniquePrivateName("#foo")
name2 := ec.Factory.NewUniquePrivateName("#foo")
g := &printer.NameGenerator{Context: ec}
text1 := g.GenerateName(name1)
text2 := g.GenerateName(name2)
assert.Equal(t, "#foo_1", text1)
assert.Equal(t, "#foo_2", text2)
}
func TestUniquePrivateName2(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
name1 := ec.Factory.NewUniquePrivateName("#foo")
g := &printer.NameGenerator{Context: ec}
text1 := g.GenerateName(name1)
text2 := g.GenerateName(name1)
assert.Equal(t, "#foo_1", text1)
assert.Equal(t, "#foo_1", text2)
}
func TestUniquePrivateNameScoped(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
name1 := ec.Factory.NewUniquePrivateName("#foo")
name2 := ec.Factory.NewUniquePrivateName("#foo")
g := &printer.NameGenerator{Context: ec}
assert.Equal(t, "#foo_1", g.GenerateName(name1))
g.PushScope(false) // private names are always reserved in nested scopes
assert.Equal(t, "#foo_2", g.GenerateName(name2))
g.PopScope(false)
}
func TestGeneratedNameForIdentifier1(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("function f() {}", false /*jsx*/)
binder.BindSourceFile(file)
n := file.Statements.Nodes[0].Name()
name1 := ec.Factory.NewGeneratedNameForNode(n)
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
text1 := g.GenerateName(name1)
assert.Equal(t, "f_1", text1)
}
func TestGeneratedNameForIdentifier2(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("function f() {}", false /*jsx*/)
binder.BindSourceFile(file)
n := file.Statements.Nodes[0].Name()
name1 := ec.Factory.NewGeneratedNameForNodeEx(n, printer.AutoGenerateOptions{
Prefix: "a",
Suffix: "b",
})
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
text1 := g.GenerateName(name1)
assert.Equal(t, "afb", text1)
}
func TestGeneratedNameForIdentifier3(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("function f() {}", false /*jsx*/)
binder.BindSourceFile(file)
n := file.Statements.Nodes[0].Name()
name1 := ec.Factory.NewGeneratedNameForNodeEx(n, printer.AutoGenerateOptions{
Prefix: "a",
Suffix: "b",
})
name2 := ec.Factory.NewGeneratedNameForNode(name1)
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
text1 := g.GenerateName(name2)
assert.Equal(t, "afb_1", text1)
}
// namespace reuses name if it does not collide with locals
func TestGeneratedNameForNamespace1(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("namespace foo { }", false /*jsx*/)
binder.BindSourceFile(file)
ns1 := file.Statements.Nodes[0]
name1 := ec.Factory.NewGeneratedNameForNode(ns1)
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
text1 := g.GenerateName(name1)
assert.Equal(t, "foo", text1)
}
// namespace uses generated name if it collides with locals
func TestGeneratedNameForNamespace2(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("namespace foo { var foo; }", false /*jsx*/)
binder.BindSourceFile(file)
ns1 := file.Statements.Nodes[0]
name1 := ec.Factory.NewGeneratedNameForNode(ns1)
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
text1 := g.GenerateName(name1)
assert.Equal(t, "foo_1", text1)
}
// avoids collisions when unscoped
func TestGeneratedNameForNamespace3(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("namespace ns1 { namespace foo { var foo; } } namespace ns2 { namespace foo { var foo; } }", false /*jsx*/)
binder.BindSourceFile(file)
ns1 := file.Statements.Nodes[0].AsModuleDeclaration().Body.AsModuleBlock().Statements.Nodes[0]
ns2 := file.Statements.Nodes[1].AsModuleDeclaration().Body.AsModuleBlock().Statements.Nodes[0]
name1 := ec.Factory.NewGeneratedNameForNode(ns1)
name2 := ec.Factory.NewGeneratedNameForNode(ns2)
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
text1 := g.GenerateName(name1)
text2 := g.GenerateName(name2)
assert.Equal(t, "foo_1", text1)
assert.Equal(t, "foo_2", text2)
}
// reuse name when scoped
func TestGeneratedNameForNamespace4(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("namespace ns1 { namespace foo { var foo; } } namespace ns2 { namespace foo { var foo; } }", false /*jsx*/)
binder.BindSourceFile(file)
ns1 := file.Statements.Nodes[0].AsModuleDeclaration().Body.AsModuleBlock().Statements.Nodes[0]
ns2 := file.Statements.Nodes[1].AsModuleDeclaration().Body.AsModuleBlock().Statements.Nodes[0]
name1 := ec.Factory.NewGeneratedNameForNode(ns1)
name2 := ec.Factory.NewGeneratedNameForNode(ns2)
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
g.PushScope(false)
text1 := g.GenerateName(name1)
g.PopScope(false)
g.PushScope(false)
text2 := g.GenerateName(name2)
g.PopScope(false)
assert.Equal(t, "foo_1", text1)
assert.Equal(t, "foo_2", text2) // Matches Strada, but is incorrect
// assert.Equal(t, "foo_1", text2) // TODO: Fix after Strada port is complete.
}
func TestGeneratedNameForNodeCached(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("namespace foo { var foo; }", false /*jsx*/)
binder.BindSourceFile(file)
ns1 := file.Statements.Nodes[0]
name1 := ec.Factory.NewGeneratedNameForNode(ns1)
name2 := ec.Factory.NewGeneratedNameForNode(ns1)
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
text1 := g.GenerateName(name1)
text2 := g.GenerateName(name2)
assert.Equal(t, "foo_1", text1)
assert.Equal(t, "foo_1", text2)
}
func TestGeneratedNameForImport(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("import * as foo from 'foo'", false /*jsx*/)
binder.BindSourceFile(file)
n := file.Statements.Nodes[0]
name1 := ec.Factory.NewGeneratedNameForNode(n)
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
text1 := g.GenerateName(name1)
assert.Equal(t, "foo_1", text1)
}
func TestGeneratedNameForExport(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("export * as foo from 'foo'", false /*jsx*/)
binder.BindSourceFile(file)
n := file.Statements.Nodes[0]
name1 := ec.Factory.NewGeneratedNameForNode(n)
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
text1 := g.GenerateName(name1)
assert.Equal(t, "foo_1", text1)
}
func TestGeneratedNameForFunctionDeclaration1(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("export function f() {}", false /*jsx*/)
binder.BindSourceFile(file)
n := file.Statements.Nodes[0]
name1 := ec.Factory.NewGeneratedNameForNode(n)
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
text1 := g.GenerateName(name1)
assert.Equal(t, "f_1", text1)
}
func TestGeneratedNameForFunctionDeclaration2(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("export default function () {}", false /*jsx*/)
binder.BindSourceFile(file)
n := file.Statements.Nodes[0]
name1 := ec.Factory.NewGeneratedNameForNode(n)
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
text1 := g.GenerateName(name1)
assert.Equal(t, "default_1", text1)
}
func TestGeneratedNameForClassDeclaration1(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("export class C {}", false /*jsx*/)
binder.BindSourceFile(file)
n := file.Statements.Nodes[0]
name1 := ec.Factory.NewGeneratedNameForNode(n)
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
text1 := g.GenerateName(name1)
assert.Equal(t, "C_1", text1)
}
func TestGeneratedNameForClassDeclaration2(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("export default class {}", false /*jsx*/)
binder.BindSourceFile(file)
n := file.Statements.Nodes[0]
name1 := ec.Factory.NewGeneratedNameForNode(n)
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
text1 := g.GenerateName(name1)
assert.Equal(t, "default_1", text1)
}
func TestGeneratedNameForExportAssignment(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("export default 0", false /*jsx*/)
binder.BindSourceFile(file)
n := file.Statements.Nodes[0]
name1 := ec.Factory.NewGeneratedNameForNode(n)
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
text1 := g.GenerateName(name1)
assert.Equal(t, "default_1", text1)
}
func TestGeneratedNameForClassExpression(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("(class {})", false /*jsx*/)
binder.BindSourceFile(file)
n := file.Statements.Nodes[0].AsExpressionStatement().Expression.AsParenthesizedExpression().Expression
name1 := ec.Factory.NewGeneratedNameForNode(n)
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
text1 := g.GenerateName(name1)
assert.Equal(t, "class_1", text1)
}
func TestGeneratedNameForMethod1(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("class C { m() {} }", false /*jsx*/)
binder.BindSourceFile(file)
n := file.Statements.Nodes[0].AsClassDeclaration().Members.Nodes[0]
name1 := ec.Factory.NewGeneratedNameForNode(n)
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
text1 := g.GenerateName(name1)
assert.Equal(t, "m_1", text1)
}
func TestGeneratedNameForMethod2(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("class C { 0() {} }", false /*jsx*/)
binder.BindSourceFile(file)
n := file.Statements.Nodes[0].AsClassDeclaration().Members.Nodes[0]
name1 := ec.Factory.NewGeneratedNameForNode(n)
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
text1 := g.GenerateName(name1)
assert.Equal(t, "_a", text1)
}
func TestGeneratedPrivateNameForMethod(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("class C { m() {} }", false /*jsx*/)
binder.BindSourceFile(file)
n := file.Statements.Nodes[0].AsClassDeclaration().Members.Nodes[0]
name1 := ec.Factory.NewGeneratedPrivateNameForNode(n)
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
text1 := g.GenerateName(name1)
assert.Equal(t, "#m_1", text1)
}
func TestGeneratedNameForComputedPropertyName(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("class C { [x] }", false /*jsx*/)
binder.BindSourceFile(file)
n := file.Statements.Nodes[0].AsClassDeclaration().Members.Nodes[0].Name()
name1 := ec.Factory.NewGeneratedNameForNode(n)
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
text1 := g.GenerateName(name1)
assert.Equal(t, "_a", text1)
}
func TestGeneratedNameForOther(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("class C { [x] }", false /*jsx*/)
binder.BindSourceFile(file)
n := ec.Factory.NewObjectLiteralExpression(
ec.Factory.NewNodeList([]*ast.Node{}),
false, /*multiLine*/
)
name1 := ec.Factory.NewGeneratedNameForNode(n)
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
text1 := g.GenerateName(name1)
assert.Equal(t, "_a", text1)
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,158 +0,0 @@
package printer
import (
"strings"
"sync"
"unicode/utf8"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/stringutil"
)
var singleLineStringWriterPool sync.Pool = sync.Pool{
New: func() any {
return &singleLineStringWriter{}
},
}
var _ EmitTextWriter = &singleLineStringWriter{}
func GetSingleLineStringWriter() (EmitTextWriter, func()) {
w := singleLineStringWriterPool.Get().(*singleLineStringWriter)
w.Clear()
return w, func() {
singleLineStringWriterPool.Put(w)
}
}
type singleLineStringWriter struct {
builder strings.Builder
lastWritten string
}
func (w *singleLineStringWriter) Clear() {
w.lastWritten = ""
w.builder.Reset()
}
func (w singleLineStringWriter) DecreaseIndent() {
// Do Nothing
}
func (w singleLineStringWriter) GetColumn() int {
return 0
}
func (w singleLineStringWriter) GetIndent() int {
return 0
}
func (w singleLineStringWriter) GetLine() int {
return 0
}
func (w singleLineStringWriter) String() string {
return w.builder.String()
}
func (w singleLineStringWriter) GetTextPos() int {
return w.builder.Len()
}
func (w singleLineStringWriter) HasTrailingComment() bool {
return false
}
func (w singleLineStringWriter) HasTrailingWhitespace() bool {
if w.builder.Len() == 0 {
return false
}
ch, _ := utf8.DecodeLastRuneInString(w.lastWritten)
if ch == utf8.RuneError {
return false
}
return stringutil.IsWhiteSpaceLike(ch)
}
func (w singleLineStringWriter) IncreaseIndent() {
// Do Nothing
}
func (w singleLineStringWriter) IsAtStartOfLine() bool {
return false
}
func (w *singleLineStringWriter) RawWrite(s string) {
w.lastWritten = s
w.builder.WriteString(s)
}
func (w *singleLineStringWriter) Write(s string) {
w.lastWritten = s
w.builder.WriteString(s)
}
func (w *singleLineStringWriter) WriteComment(text string) {
w.lastWritten = text
w.builder.WriteString(text)
}
func (w *singleLineStringWriter) WriteKeyword(text string) {
w.lastWritten = text
w.builder.WriteString(text)
}
func (w *singleLineStringWriter) WriteLine() {
w.lastWritten = " "
w.builder.WriteString(" ")
}
func (w *singleLineStringWriter) WriteLineForce(force bool) {
w.lastWritten = " "
w.builder.WriteString(" ")
}
func (w *singleLineStringWriter) WriteLiteral(s string) {
w.lastWritten = s
w.builder.WriteString(s)
}
func (w *singleLineStringWriter) WriteOperator(text string) {
w.lastWritten = text
w.builder.WriteString(text)
}
func (w *singleLineStringWriter) WriteParameter(text string) {
w.lastWritten = text
w.builder.WriteString(text)
}
func (w *singleLineStringWriter) WriteProperty(text string) {
w.lastWritten = text
w.builder.WriteString(text)
}
func (w *singleLineStringWriter) WritePunctuation(text string) {
w.lastWritten = text
w.builder.WriteString(text)
}
func (w *singleLineStringWriter) WriteSpace(text string) {
w.lastWritten = text
w.builder.WriteString(text)
}
func (w *singleLineStringWriter) WriteStringLiteral(text string) {
w.lastWritten = text
w.builder.WriteString(text)
}
func (w *singleLineStringWriter) WriteSymbol(text string, symbol *ast.Symbol) {
w.lastWritten = text
w.builder.WriteString(text)
}
func (w *singleLineStringWriter) WriteTrailingSemicolon(text string) {
w.lastWritten = text
w.builder.WriteString(text)
}

View File

@ -1,10 +0,0 @@
package printer
import (
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath"
)
type SourceFileMetaDataProvider interface {
GetSourceFileMetaData(path tspath.Path) *ast.SourceFileMetaData
}

View File

@ -1,211 +0,0 @@
package printer
import (
"strings"
"unicode/utf8"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/stringutil"
)
var _ EmitTextWriter = &textWriter{}
type textWriter struct {
newLine string
builder strings.Builder
lastWritten string
indent int
lineStart bool
lineCount int
linePos int
hasTrailingCommentState bool
}
func (w *textWriter) Clear() {
*w = textWriter{newLine: w.newLine, lineStart: true}
}
func (w *textWriter) DecreaseIndent() {
w.indent--
}
func (w *textWriter) GetColumn() int {
if w.lineStart {
return w.indent * 4
}
return w.builder.Len() - w.linePos
}
func (w *textWriter) GetIndent() int {
return w.indent
}
func (w *textWriter) GetLine() int {
return w.lineCount
}
func (w *textWriter) String() string {
return w.builder.String()
}
func (w *textWriter) GetTextPos() int {
return w.builder.Len()
}
func (w textWriter) HasTrailingComment() bool {
return w.hasTrailingCommentState
}
func (w *textWriter) HasTrailingWhitespace() bool {
if w.builder.Len() == 0 {
return false
}
ch, _ := utf8.DecodeLastRuneInString(w.lastWritten)
if ch == utf8.RuneError {
return false
}
return stringutil.IsWhiteSpaceLike(ch)
}
func (w *textWriter) IncreaseIndent() {
w.indent++
}
func (w *textWriter) IsAtStartOfLine() bool {
return w.lineStart
}
func (w *textWriter) RawWrite(s string) {
if s != "" {
w.builder.WriteString(s)
w.lastWritten = s
w.updateLineCountAndPosFor(s)
w.hasTrailingCommentState = false
}
}
func (w *textWriter) updateLineCountAndPosFor(s string) {
var count int
var lastLineStart core.TextPos
for lineStart := range core.ComputeECMALineStartsSeq(s) {
count++
lastLineStart = lineStart
}
if count > 1 {
w.lineCount += count - 1
curLen := w.builder.Len()
w.linePos = curLen - len(s) + int(lastLineStart)
w.lineStart = (w.linePos - curLen) == 0
return
}
w.lineStart = false
}
func getIndentString(indent int) string {
switch indent {
case 0:
return ""
case 1:
return " "
default:
// TODO: This is cached in tsc - should it be cached here?
return strings.Repeat(" ", indent)
}
}
func (w *textWriter) writeText(s string) {
if s != "" {
if w.lineStart {
w.builder.WriteString(getIndentString(w.indent))
w.lineStart = false
}
w.builder.WriteString(s)
w.lastWritten = s
w.updateLineCountAndPosFor(s)
}
}
func (w *textWriter) Write(s string) {
if s != "" {
w.hasTrailingCommentState = false
}
w.writeText(s)
}
func (w *textWriter) WriteComment(text string) {
if text != "" {
w.hasTrailingCommentState = true
}
w.writeText(text)
}
func (w *textWriter) WriteKeyword(text string) {
w.Write(text)
}
func (w *textWriter) writeLineRaw() {
w.builder.WriteString(w.newLine)
w.lastWritten = w.newLine
w.lineCount++
w.linePos = w.builder.Len()
w.lineStart = true
w.hasTrailingCommentState = false
}
func (w *textWriter) WriteLine() {
if !w.lineStart {
w.writeLineRaw()
}
}
func (w *textWriter) WriteLineForce(force bool) {
if !w.lineStart || force {
w.writeLineRaw()
}
}
func (w *textWriter) WriteLiteral(s string) {
w.Write(s)
}
func (w *textWriter) WriteOperator(text string) {
w.Write(text)
}
func (w *textWriter) WriteParameter(text string) {
w.Write(text)
}
func (w *textWriter) WriteProperty(text string) {
w.Write(text)
}
func (w *textWriter) WritePunctuation(text string) {
w.Write(text)
}
func (w *textWriter) WriteSpace(text string) {
w.Write(text)
}
func (w *textWriter) WriteStringLiteral(text string) {
w.Write(text)
}
func (w *textWriter) WriteSymbol(text string, symbol *ast.Symbol) {
w.Write(text)
}
func (w *textWriter) WriteTrailingSemicolon(text string) {
w.Write(text)
}
func NewTextWriter(newLine string) EmitTextWriter {
var w textWriter
w.newLine = newLine
w.Clear()
return &w
}

View File

@ -1,909 +0,0 @@
package printer
import (
"fmt"
"slices"
"strconv"
"strings"
"unicode/utf8"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/scanner"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/stringutil"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath"
)
type getLiteralTextFlags int
const (
getLiteralTextFlagsNone getLiteralTextFlags = 0
getLiteralTextFlagsNeverAsciiEscape getLiteralTextFlags = 1 << 0
getLiteralTextFlagsJsxAttributeEscape getLiteralTextFlags = 1 << 1
getLiteralTextFlagsTerminateUnterminatedLiterals getLiteralTextFlags = 1 << 2
getLiteralTextFlagsAllowNumericSeparator getLiteralTextFlags = 1 << 3
)
type QuoteChar rune
const (
QuoteCharSingleQuote QuoteChar = '\''
QuoteCharDoubleQuote QuoteChar = '"'
QuoteCharBacktick QuoteChar = '`'
)
var jsxEscapedCharsMap = map[rune]string{
'"': "&quot;",
'\'': "&apos;",
}
var escapedCharsMap = map[rune]string{
'\t': `\t`,
'\v': `\v`,
'\f': `\f`,
'\b': `\b`,
'\r': `\r`,
'\n': `\n`,
'\\': `\\`,
'"': `\"`,
'\'': `\'`,
'`': "\\`",
'$': `\$`, // when quoteChar == '`'
'\u2028': `\u2028`, // lineSeparator
'\u2029': `\u2029`, // paragraphSeparator
'\u0085': `\u0085`, // nextLine
}
func encodeJsxCharacterEntity(b *strings.Builder, charCode rune) {
hexCharCode := strings.ToUpper(strconv.FormatUint(uint64(charCode), 16))
b.WriteString("&#x")
b.WriteString(hexCharCode)
b.WriteByte(';')
}
func encodeUtf16EscapeSequence(b *strings.Builder, charCode rune) {
hexCharCode := strings.ToUpper(strconv.FormatUint(uint64(charCode), 16))
b.WriteString(`\u`)
for i := len(hexCharCode); i < 4; i++ {
b.WriteByte('0')
}
b.WriteString(hexCharCode)
}
// Based heavily on the abstract 'Quote'/'QuoteJSONString' operation from ECMA-262 (24.3.2.2),
// but augmented for a few select characters (e.g. lineSeparator, paragraphSeparator, nextLine)
// Note that this doesn't actually wrap the input in double quotes.
func escapeStringWorker(s string, quoteChar QuoteChar, flags getLiteralTextFlags, b *strings.Builder) {
pos := 0
i := 0
for i < len(s) {
ch, size := utf8.DecodeRuneInString(s[i:])
escape := false
// This consists of the first 19 unprintable ASCII characters, canonical escapes, lineSeparator,
// paragraphSeparator, and nextLine. The latter three are just desirable to suppress new lines in
// the language service. These characters should be escaped when printing, and if any characters are added,
// `escapedCharsMap` and/or `jsxEscapedCharsMap` must be updated. Note that this *does not* include the 'delete'
// character. There is no reason for this other than that JSON.stringify does not handle it either.
switch ch {
case '\\':
if flags&getLiteralTextFlagsJsxAttributeEscape == 0 {
escape = true
}
case '$':
if quoteChar == QuoteCharBacktick && i+1 < len(s) && s[i+1] == '{' {
escape = true
}
case rune(quoteChar), '\u2028', '\u2029', '\u0085', '\r':
escape = true
case '\n':
if quoteChar != QuoteCharBacktick {
// Template strings preserve simple LF newlines, still encode CRLF (or CR).
escape = true
}
default:
if ch <= '\u001f' || flags&getLiteralTextFlagsNeverAsciiEscape == 0 && ch > '\u007f' {
escape = true
}
}
if escape {
if pos < i {
// Write string up to this point
b.WriteString(s[pos:i])
}
switch {
case flags&getLiteralTextFlagsJsxAttributeEscape != 0:
if ch == 0 {
b.WriteString("&#0;")
} else if match, ok := jsxEscapedCharsMap[ch]; ok {
b.WriteString(match)
} else {
encodeJsxCharacterEntity(b, ch)
}
default:
if ch == '\r' && quoteChar == QuoteCharBacktick && i+1 < len(s) && s[i+1] == '\n' {
// Template strings preserve simple LF newlines, but still must escape CRLF. Left alone, the
// above cases for `\r` and `\n` would inadvertently escape CRLF as two independent characters.
size++
b.WriteString(`\r\n`)
} else if ch > 0xffff {
// encode as surrogate pair
ch -= 0x10000
encodeUtf16EscapeSequence(b, (ch&0b11111111110000000000>>10)+0xD800)
encodeUtf16EscapeSequence(b, (ch&0b00000000001111111111)+0xDC00)
} else if ch == 0 {
if i+1 < len(s) && stringutil.IsDigit(rune(s[i+1])) {
// If the null character is followed by digits, print as a hex escape to prevent the result from
// parsing as an octal (which is forbidden in strict mode)
b.WriteString(`\x00`)
} else {
// Otherwise, keep printing a literal \0 for the null character
b.WriteString(`\0`)
}
} else {
if match, ok := escapedCharsMap[ch]; ok {
b.WriteString(match)
} else {
encodeUtf16EscapeSequence(b, ch)
}
}
}
pos = i + size
}
i += size
}
if pos < i {
b.WriteString(s[pos:])
}
}
func EscapeString(s string, quoteChar QuoteChar) string {
var b strings.Builder
b.Grow(len(s) + 2)
escapeStringWorker(s, quoteChar, getLiteralTextFlagsNeverAsciiEscape, &b)
return b.String()
}
func escapeNonAsciiString(s string, quoteChar QuoteChar) string {
var b strings.Builder
b.Grow(len(s) + 2)
escapeStringWorker(s, quoteChar, getLiteralTextFlagsNone, &b)
return b.String()
}
func escapeJsxAttributeString(s string, quoteChar QuoteChar) string {
var b strings.Builder
b.Grow(len(s) + 2)
escapeStringWorker(s, quoteChar, getLiteralTextFlagsJsxAttributeEscape|getLiteralTextFlagsNeverAsciiEscape, &b)
return b.String()
}
func canUseOriginalText(node *ast.LiteralLikeNode, flags getLiteralTextFlags) bool {
// A synthetic node has no original text, nor does a node without a parent as we would be unable to find the
// containing SourceFile. We also cannot use the original text if the literal was unterminated and the caller has
// requested proper termination of unterminated literals
if ast.NodeIsSynthesized(node) || node.Parent == nil || flags&getLiteralTextFlagsTerminateUnterminatedLiterals != 0 && ast.IsUnterminatedLiteral(node) {
return false
}
if node.Kind == ast.KindNumericLiteral {
tokenFlags := node.AsNumericLiteral().TokenFlags
// For a numeric literal, we cannot use the original text if the original text was an invalid literal
if tokenFlags&ast.TokenFlagsIsInvalid != 0 {
return false
}
// We also cannot use the original text if the literal contains numeric separators, but numeric separators
// are not permitted
if tokenFlags&ast.TokenFlagsContainsSeparator != 0 {
return flags&getLiteralTextFlagsAllowNumericSeparator != 0
}
}
// Finally, we do not use the original text of a BigInt literal
// TODO(rbuckton): The reason as to why we do not use the original text for bigints is not mentioned in the
// original compiler source. It could be that this is no longer necessary, in which case bigint literals should
// use the same code path as numeric literals, above
return node.Kind != ast.KindBigIntLiteral
}
func getLiteralText(node *ast.LiteralLikeNode, sourceFile *ast.SourceFile, flags getLiteralTextFlags) string {
// If we don't need to downlevel and we can reach the original source text using
// the node's parent reference, then simply get the text as it was originally written.
if sourceFile != nil && canUseOriginalText(node, flags) {
return scanner.GetSourceTextOfNodeFromSourceFile(sourceFile, node, false /*includeTrivia*/)
}
// If we can't reach the original source text, use the canonical form if it's a number,
// or a (possibly escaped) quoted form of the original text if it's string-like.
switch node.Kind {
case ast.KindStringLiteral:
var b strings.Builder
var quoteChar QuoteChar
if node.AsStringLiteral().TokenFlags&ast.TokenFlagsSingleQuote != 0 {
quoteChar = QuoteCharSingleQuote
} else {
quoteChar = QuoteCharDoubleQuote
}
text := node.Text()
// Write leading quote character
b.Grow(len(text) + 2)
b.WriteRune(rune(quoteChar))
// Write text
escapeStringWorker(text, quoteChar, flags, &b)
// Write trailing quote character
b.WriteRune(rune(quoteChar))
return b.String()
case ast.KindNoSubstitutionTemplateLiteral,
ast.KindTemplateHead,
ast.KindTemplateMiddle,
ast.KindTemplateTail:
// If a NoSubstitutionTemplateLiteral appears to have a substitution in it, the original text
// had to include a backslash: `not \${a} substitution`.
var b strings.Builder
text := node.TemplateLiteralLikeData().Text
rawText := node.TemplateLiteralLikeData().RawText
raw := len(rawText) > 0 || len(text) == 0
var textLen int
if raw {
textLen = len(rawText)
} else {
textLen = len(text)
}
// Write leading quote character
switch node.Kind {
case ast.KindNoSubstitutionTemplateLiteral:
b.Grow(2 + textLen)
b.WriteRune('`')
case ast.KindTemplateHead:
b.Grow(3 + textLen)
b.WriteRune('`')
case ast.KindTemplateMiddle:
b.Grow(3 + textLen)
b.WriteRune('}')
case ast.KindTemplateTail:
b.Grow(2 + textLen)
b.WriteRune('}')
}
// Write text
switch {
case len(rawText) > 0 || len(text) == 0:
// If rawText is set, it is expected to be valid.
b.WriteString(rawText)
default:
escapeStringWorker(text, QuoteCharBacktick, flags, &b)
}
// Write trailing quote character
switch node.Kind {
case ast.KindNoSubstitutionTemplateLiteral:
b.WriteRune('`')
case ast.KindTemplateHead:
b.WriteString("${")
case ast.KindTemplateMiddle:
b.WriteString("${")
case ast.KindTemplateTail:
b.WriteRune('`')
}
return b.String()
case ast.KindNumericLiteral, ast.KindBigIntLiteral:
return node.Text()
case ast.KindRegularExpressionLiteral:
if flags&getLiteralTextFlagsTerminateUnterminatedLiterals != 0 && ast.IsUnterminatedLiteral(node) {
var b strings.Builder
text := node.Text()
if len(text) > 0 && text[len(text)-1] == '\\' {
b.Grow(2 + len(text))
b.WriteString(text)
b.WriteString(" /")
} else {
b.Grow(1 + len(text))
b.WriteString(text)
b.WriteString("/")
}
return b.String()
}
return node.Text()
default:
panic("Unsupported LiteralLikeNode")
}
}
func isNotPrologueDirective(node *ast.Node) bool {
return !ast.IsPrologueDirective(node)
}
func rangeIsOnSingleLine(r core.TextRange, sourceFile *ast.SourceFile) bool {
return rangeStartIsOnSameLineAsRangeEnd(r, r, sourceFile)
}
func rangeStartPositionsAreOnSameLine(range1 core.TextRange, range2 core.TextRange, sourceFile *ast.SourceFile) bool {
return positionsAreOnSameLine(
getStartPositionOfRange(range1, sourceFile, false /*includeComments*/),
getStartPositionOfRange(range2, sourceFile, false /*includeComments*/),
sourceFile,
)
}
func rangeEndPositionsAreOnSameLine(range1 core.TextRange, range2 core.TextRange, sourceFile *ast.SourceFile) bool {
return positionsAreOnSameLine(range1.End(), range2.End(), sourceFile)
}
func rangeStartIsOnSameLineAsRangeEnd(range1 core.TextRange, range2 core.TextRange, sourceFile *ast.SourceFile) bool {
return positionsAreOnSameLine(getStartPositionOfRange(range1, sourceFile, false /*includeComments*/), range2.End(), sourceFile)
}
func rangeEndIsOnSameLineAsRangeStart(range1 core.TextRange, range2 core.TextRange, sourceFile *ast.SourceFile) bool {
return positionsAreOnSameLine(range1.End(), getStartPositionOfRange(range2, sourceFile, false /*includeComments*/), sourceFile)
}
func getStartPositionOfRange(r core.TextRange, sourceFile *ast.SourceFile, includeComments bool) int {
if ast.PositionIsSynthesized(r.Pos()) {
return -1
}
return scanner.SkipTriviaEx(sourceFile.Text(), r.Pos(), &scanner.SkipTriviaOptions{StopAtComments: includeComments})
}
func positionsAreOnSameLine(pos1 int, pos2 int, sourceFile *ast.SourceFile) bool {
return GetLinesBetweenPositions(sourceFile, pos1, pos2) == 0
}
func GetLinesBetweenPositions(sourceFile *ast.SourceFile, pos1 int, pos2 int) int {
if pos1 == pos2 {
return 0
}
lineStarts := scanner.GetECMALineStarts(sourceFile)
lower := core.IfElse(pos1 < pos2, pos1, pos2)
isNegative := lower == pos2
upper := core.IfElse(isNegative, pos1, pos2)
lowerLine := scanner.ComputeLineOfPosition(lineStarts, lower)
upperLine := lowerLine + scanner.ComputeLineOfPosition(lineStarts[lowerLine:], upper)
if isNegative {
return lowerLine - upperLine
} else {
return upperLine - lowerLine
}
}
func getLinesBetweenRangeEndAndRangeStart(range1 core.TextRange, range2 core.TextRange, sourceFile *ast.SourceFile, includeSecondRangeComments bool) int {
range2Start := getStartPositionOfRange(range2, sourceFile, includeSecondRangeComments)
return GetLinesBetweenPositions(sourceFile, range1.End(), range2Start)
}
func getLinesBetweenPositionAndPrecedingNonWhitespaceCharacter(pos int, stopPos int, sourceFile *ast.SourceFile, includeComments bool) int {
startPos := scanner.SkipTriviaEx(sourceFile.Text(), pos, &scanner.SkipTriviaOptions{StopAtComments: includeComments})
prevPos := getPreviousNonWhitespacePosition(startPos, stopPos, sourceFile)
return GetLinesBetweenPositions(sourceFile, core.IfElse(prevPos >= 0, prevPos, stopPos), startPos)
}
func getLinesBetweenPositionAndNextNonWhitespaceCharacter(pos int, stopPos int, sourceFile *ast.SourceFile, includeComments bool) int {
nextPos := scanner.SkipTriviaEx(sourceFile.Text(), pos, &scanner.SkipTriviaOptions{StopAtComments: includeComments})
return GetLinesBetweenPositions(sourceFile, pos, core.IfElse(stopPos < nextPos, stopPos, nextPos))
}
func getPreviousNonWhitespacePosition(pos int, stopPos int, sourceFile *ast.SourceFile) int {
for ; pos >= stopPos; pos-- {
if !stringutil.IsWhiteSpaceLike(rune(sourceFile.Text()[pos])) {
return pos
}
}
return -1
}
func getCommentRange(node *ast.Node) core.TextRange {
// TODO(rbuckton)
return node.Loc
}
func siblingNodePositionsAreComparable(previousNode *ast.Node, nextNode *ast.Node) bool {
if nextNode.Pos() < previousNode.End() {
return false
}
// TODO(rbuckton)
// previousNode = getOriginalNode(previousNode);
// nextNode = getOriginalNode(nextNode);
parent := previousNode.Parent
if parent == nil || parent != nextNode.Parent {
return false
}
parentNodeArray := getContainingNodeArray(previousNode)
if parentNodeArray != nil {
prevNodeIndex := slices.Index(parentNodeArray.Nodes, previousNode)
return prevNodeIndex >= 0 && slices.Index(parentNodeArray.Nodes, nextNode) == prevNodeIndex+1
}
return false
}
func getContainingNodeArray(node *ast.Node) *ast.NodeList {
parent := node.Parent
if parent == nil {
return nil
}
switch node.Kind {
case ast.KindTypeParameter:
switch {
case ast.IsFunctionLike(parent):
return parent.FunctionLikeData().TypeParameters
case ast.IsClassLike(parent):
return parent.ClassLikeData().TypeParameters
case ast.IsInterfaceDeclaration(parent):
return parent.AsInterfaceDeclaration().TypeParameters
case ast.IsTypeOrJSTypeAliasDeclaration(parent):
return parent.AsTypeAliasDeclaration().TypeParameters
case ast.IsInferTypeNode(parent):
break
default:
panic(fmt.Sprintf("Unexpected TypeParameter parent: %#v", parent.Kind))
}
case ast.KindParameter:
return node.Parent.FunctionLikeData().Parameters
case ast.KindTemplateLiteralTypeSpan:
return node.Parent.AsTemplateLiteralTypeNode().TemplateSpans
case ast.KindTemplateSpan:
return node.Parent.AsTemplateExpression().TemplateSpans
case ast.KindDecorator:
if canHaveDecorators(node.Parent) {
if modifiers := node.Parent.Modifiers(); modifiers != nil {
return &modifiers.NodeList
}
}
return nil
case ast.KindHeritageClause:
if ast.IsClassLike(node.Parent) {
return node.Parent.ClassLikeData().HeritageClauses
} else {
return node.Parent.AsInterfaceDeclaration().HeritageClauses
}
}
// TODO(rbuckton)
// if ast.IsJSDocTag(node) {
// if ast.IsJSDocTypeLiteral(node.parent) {
// return nil
// }
// return node.parent.tags
// }
switch parent.Kind {
case ast.KindTypeLiteral:
if ast.IsTypeElement(node) {
return parent.AsTypeLiteralNode().Members
}
case ast.KindInterfaceDeclaration:
if ast.IsTypeElement(node) {
return parent.AsInterfaceDeclaration().Members
}
case ast.KindUnionType:
return parent.AsUnionTypeNode().Types
case ast.KindIntersectionType:
return parent.AsIntersectionTypeNode().Types
case ast.KindTupleType:
return parent.AsTupleTypeNode().Elements
case ast.KindArrayLiteralExpression:
return parent.AsArrayLiteralExpression().Elements
case ast.KindCommaListExpression:
panic("not implemented")
case ast.KindNamedImports:
return parent.AsNamedImports().Elements
case ast.KindNamedExports:
return parent.AsNamedExports().Elements
case ast.KindObjectLiteralExpression:
return parent.AsObjectLiteralExpression().Properties
case ast.KindJsxAttributes:
return parent.AsJsxAttributes().Properties
case ast.KindCallExpression:
p := parent.AsCallExpression()
switch {
case ast.IsTypeNode(node):
return p.TypeArguments
case node != p.Expression:
return p.Arguments
}
case ast.KindNewExpression:
p := parent.AsNewExpression()
switch {
case ast.IsTypeNode(node):
return p.TypeArguments
case node != p.Expression:
return p.Arguments
}
case ast.KindJsxElement:
if ast.IsJsxChild(node) {
return parent.AsJsxElement().Children
}
case ast.KindJsxFragment:
if ast.IsJsxChild(node) {
return parent.AsJsxFragment().Children
}
case ast.KindJsxOpeningElement:
if ast.IsTypeNode(node) {
return parent.AsJsxOpeningElement().TypeArguments
}
case ast.KindJsxSelfClosingElement:
if ast.IsTypeNode(node) {
return parent.AsJsxSelfClosingElement().TypeArguments
}
case ast.KindBlock:
return parent.AsBlock().Statements
case ast.KindCaseClause, ast.KindDefaultClause:
return parent.AsCaseOrDefaultClause().Statements
case ast.KindModuleBlock:
return parent.AsModuleBlock().Statements
case ast.KindCaseBlock:
return parent.AsCaseBlock().Clauses
case ast.KindClassDeclaration, ast.KindClassExpression:
if ast.IsClassElement(node) {
return parent.ClassLikeData().Members
}
case ast.KindEnumDeclaration:
if ast.IsEnumMember(node) {
return parent.AsEnumDeclaration().Members
}
case ast.KindSourceFile:
if ast.IsStatement(node) {
return parent.AsSourceFile().Statements
}
}
if ast.IsModifier(node) {
if modifiers := parent.Modifiers(); modifiers != nil {
return &modifiers.NodeList
}
}
return nil
}
func canHaveDecorators(node *ast.Node) bool {
switch node.Kind {
case ast.KindParameter,
ast.KindPropertyDeclaration,
ast.KindMethodDeclaration,
ast.KindGetAccessor,
ast.KindSetAccessor,
ast.KindClassExpression,
ast.KindClassDeclaration:
return true
}
return false
}
func originalNodesHaveSameParent(nodeA *ast.Node, nodeB *ast.Node) bool {
// TODO(rbuckton): nodeA = getOriginalNode(nodeA)
if nodeA.Parent != nil {
// For performance, do not call `getOriginalNode` for `nodeB` if `nodeA` doesn't even
// have a parent node.
// TODO(rbuckton): nodeB = getOriginalNode(nodeB)
return nodeA.Parent == nodeB.Parent
}
return false
}
func tryGetEnd(node interface{ End() int }) (int, bool) {
// avoid using reflect (via core.IsNil) for common cases
switch v := node.(type) {
case (*ast.Node):
if v != nil {
return v.End(), true
}
case (*ast.NodeList):
if v != nil {
return v.End(), true
}
case (*ast.ModifierList):
if v != nil {
return v.End(), true
}
case (*core.TextRange):
if v != nil {
return v.End(), true
}
case (core.TextRange):
return v.End(), true
default:
panic(fmt.Sprintf("unhandled type: %T", node))
}
return 0, false
}
func greatestEnd(end int, nodes ...interface{ End() int }) int {
for i := len(nodes) - 1; i >= 0; i-- {
node := nodes[i]
if nodeEnd, ok := tryGetEnd(node); ok && end < nodeEnd {
end = nodeEnd
}
}
return end
}
func skipSynthesizedParentheses(node *ast.Node) *ast.Node {
for node.Kind == ast.KindParenthesizedExpression && ast.NodeIsSynthesized(node) {
node = node.AsParenthesizedExpression().Expression
}
return node
}
func isNewExpressionWithoutArguments(node *ast.Node) bool {
return node.Kind == ast.KindNewExpression && node.AsNewExpression().Arguments == nil
}
func isBinaryOperation(node *ast.Node, token ast.Kind) bool {
node = ast.SkipPartiallyEmittedExpressions(node)
return node.Kind == ast.KindBinaryExpression &&
node.AsBinaryExpression().OperatorToken.Kind == token
}
func isImmediatelyInvokedFunctionExpressionOrArrowFunction(node *ast.Expression) bool {
node = ast.SkipPartiallyEmittedExpressions(node)
if !ast.IsCallExpression(node) {
return false
}
node = ast.SkipPartiallyEmittedExpressions(node.AsCallExpression().Expression)
return ast.IsFunctionExpression(node) || ast.IsArrowFunction(node)
}
func IsFileLevelUniqueName(sourceFile *ast.SourceFile, name string, hasGlobalName func(string) bool) bool {
if hasGlobalName != nil && hasGlobalName(name) {
return false
}
_, ok := sourceFile.Identifiers[name]
return !ok
}
func hasLeadingHash(text string) bool {
return len(text) > 0 && text[0] == '#'
}
func removeLeadingHash(text string) string {
if hasLeadingHash(text) {
return text[1:]
} else {
return text
}
}
func ensureLeadingHash(text string) string {
if hasLeadingHash(text) {
return text
} else {
return "#" + text
}
}
func FormatGeneratedName(privateName bool, prefix string, base string, suffix string) string {
name := removeLeadingHash(prefix) + removeLeadingHash(base) + removeLeadingHash(suffix)
if privateName {
return ensureLeadingHash(name)
}
return name
}
func isASCIIWordCharacter(ch rune) bool {
return stringutil.IsASCIILetter(ch) || stringutil.IsDigit(ch) || ch == '_'
}
func makeIdentifierFromModuleName(moduleName string) string {
moduleName = tspath.GetBaseFileName(moduleName)
var builder strings.Builder
start := 0
pos := 0
for pos < len(moduleName) {
ch := rune(moduleName[pos])
if pos == 0 && stringutil.IsDigit(ch) {
builder.WriteByte('_')
} else if !isASCIIWordCharacter(ch) {
if start < pos {
builder.WriteString(moduleName[start:pos])
}
builder.WriteByte('_')
start = pos + 1
}
pos++
}
if start < pos {
builder.WriteString(moduleName[start:pos])
}
return builder.String()
}
func findSpanEndWithEmitContext[T any](c *EmitContext, array []T, test func(c *EmitContext, value T) bool, start int) int {
i := start
for i < len(array) && test(c, array[i]) {
i++
}
return i
}
func findSpanEnd[T any](array []T, test func(value T) bool, start int) int {
i := start
for i < len(array) && test(array[i]) {
i++
}
return i
}
func skipWhiteSpaceSingleLine(text string, pos *int) {
for *pos < len(text) {
ch, size := utf8.DecodeRuneInString(text[*pos:])
if !stringutil.IsWhiteSpaceSingleLine(ch) {
break
}
*pos += size
}
}
func matchWhiteSpaceSingleLine(text string, pos *int) bool {
startPos := *pos
skipWhiteSpaceSingleLine(text, pos)
return *pos != startPos
}
func matchRune(text string, pos *int, expected rune) bool {
ch, size := utf8.DecodeRuneInString(text[*pos:])
if ch == expected {
*pos += size
return true
}
return false
}
func matchString(text string, pos *int, expected string) bool {
textPos := *pos
expectedPos := 0
for expectedPos < len(expected) {
if textPos >= len(text) {
return false
}
expectedRune, expectedSize := utf8.DecodeRuneInString(expected[expectedPos:])
if !matchRune(text, &textPos, expectedRune) {
return false
}
expectedPos += expectedSize
}
*pos = textPos
return true
}
func matchQuotedString(text string, pos *int) bool {
textPos := *pos
var quoteChar rune
switch {
case matchRune(text, &textPos, '\''):
quoteChar = '\''
case matchRune(text, &textPos, '"'):
quoteChar = '"'
default:
return false
}
for textPos < len(text) {
ch, size := utf8.DecodeRuneInString(text[textPos:])
textPos += size
if ch == quoteChar {
*pos = textPos
return true
}
}
return false
}
// /// <reference path="..." />
// /// <reference types="..." />
// /// <reference lib="..." />
// /// <reference no-default-lib="..." />
// /// <amd-dependency path="..." />
// /// <amd-module />
func IsRecognizedTripleSlashComment(text string, commentRange ast.CommentRange) bool {
if commentRange.Kind == ast.KindSingleLineCommentTrivia &&
commentRange.Len() > 2 &&
text[commentRange.Pos()+1] == '/' &&
text[commentRange.Pos()+2] == '/' {
text = text[commentRange.Pos()+3 : commentRange.End()]
pos := 0
skipWhiteSpaceSingleLine(text, &pos)
if !matchRune(text, &pos, '<') {
return false
}
switch {
case matchString(text, &pos, "reference"):
if !matchWhiteSpaceSingleLine(text, &pos) {
return false
}
if !matchString(text, &pos, "path") &&
!matchString(text, &pos, "types") &&
!matchString(text, &pos, "lib") &&
!matchString(text, &pos, "no-default-lib") {
return false
}
skipWhiteSpaceSingleLine(text, &pos)
if !matchRune(text, &pos, '=') {
return false
}
skipWhiteSpaceSingleLine(text, &pos)
if !matchQuotedString(text, &pos) {
return false
}
case matchString(text, &pos, "amd-dependency"):
if !matchWhiteSpaceSingleLine(text, &pos) {
return false
}
if !matchString(text, &pos, "path") {
return false
}
skipWhiteSpaceSingleLine(text, &pos)
if !matchRune(text, &pos, '=') {
return false
}
skipWhiteSpaceSingleLine(text, &pos)
if !matchQuotedString(text, &pos) {
return false
}
case matchString(text, &pos, "amd-module"):
skipWhiteSpaceSingleLine(text, &pos)
default:
return false
}
index := strings.Index(text[pos:], "/>")
return index != -1
}
return false
}
func isJSDocLikeText(text string, comment ast.CommentRange) bool {
return comment.Kind == ast.KindMultiLineCommentTrivia &&
comment.Len() > 5 &&
text[comment.Pos()+2] == '*' &&
text[comment.Pos()+3] != '/'
}
func IsPinnedComment(text string, comment ast.CommentRange) bool {
return comment.Kind == ast.KindMultiLineCommentTrivia &&
comment.Len() > 5 &&
text[comment.Pos()+2] == '!'
}
func calculateIndent(text string, pos int, end int) int {
currentLineIndent := 0
indentSize := len(getIndentString(1))
for pos < end {
ch, size := utf8.DecodeRuneInString(text[pos:])
if !stringutil.IsWhiteSpaceSingleLine(ch) {
break
}
if ch == '\t' {
// Tabs = TabSize = indent size and go to next tabStop
currentLineIndent += indentSize - (currentLineIndent % indentSize)
} else {
// Single space
currentLineIndent++
}
pos += size
}
return currentLineIndent
}

View File

@ -1,153 +0,0 @@
package printer
import (
"fmt"
"testing"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
"gotest.tools/v3/assert"
)
func TestEscapeString(t *testing.T) {
t.Parallel()
data := []struct {
s string
quoteChar QuoteChar
expected string
}{
{s: "", quoteChar: QuoteCharDoubleQuote, expected: ``},
{s: "abc", quoteChar: QuoteCharDoubleQuote, expected: `abc`},
{s: "ab\"c", quoteChar: QuoteCharDoubleQuote, expected: `ab\"c`},
{s: "ab\tc", quoteChar: QuoteCharDoubleQuote, expected: `ab\tc`},
{s: "ab\nc", quoteChar: QuoteCharDoubleQuote, expected: `ab\nc`},
{s: "ab'c", quoteChar: QuoteCharDoubleQuote, expected: `ab'c`},
{s: "ab'c", quoteChar: QuoteCharSingleQuote, expected: `ab\'c`},
{s: "ab\"c", quoteChar: QuoteCharSingleQuote, expected: `ab"c`},
{s: "ab`c", quoteChar: QuoteCharBacktick, expected: "ab\\`c"},
{s: "\u001f", quoteChar: QuoteCharBacktick, expected: "\\u001F"},
}
for i, rec := range data {
t.Run(fmt.Sprintf("[%d] escapeString(%q, %v)", i, rec.s, rec.quoteChar), func(t *testing.T) {
t.Parallel()
actual := EscapeString(rec.s, rec.quoteChar)
assert.Equal(t, actual, rec.expected)
})
}
}
func TestEscapeNonAsciiString(t *testing.T) {
t.Parallel()
data := []struct {
s string
quoteChar QuoteChar
expected string
}{
{s: "", quoteChar: QuoteCharDoubleQuote, expected: ``},
{s: "abc", quoteChar: QuoteCharDoubleQuote, expected: `abc`},
{s: "ab\"c", quoteChar: QuoteCharDoubleQuote, expected: `ab\"c`},
{s: "ab\tc", quoteChar: QuoteCharDoubleQuote, expected: `ab\tc`},
{s: "ab\nc", quoteChar: QuoteCharDoubleQuote, expected: `ab\nc`},
{s: "ab'c", quoteChar: QuoteCharDoubleQuote, expected: `ab'c`},
{s: "ab'c", quoteChar: QuoteCharSingleQuote, expected: `ab\'c`},
{s: "ab\"c", quoteChar: QuoteCharSingleQuote, expected: `ab"c`},
{s: "ab`c", quoteChar: QuoteCharBacktick, expected: "ab\\`c"},
{s: "ab\u008fc", quoteChar: QuoteCharDoubleQuote, expected: `ab\u008Fc`},
{s: "𝟘𝟙", quoteChar: QuoteCharDoubleQuote, expected: `\uD835\uDFD8\uD835\uDFD9`},
}
for i, rec := range data {
t.Run(fmt.Sprintf("[%d] escapeNonAsciiString(%q, %v)", i, rec.s, rec.quoteChar), func(t *testing.T) {
t.Parallel()
actual := escapeNonAsciiString(rec.s, rec.quoteChar)
assert.Equal(t, actual, rec.expected)
})
}
}
func TestEscapeJsxAttributeString(t *testing.T) {
t.Parallel()
data := []struct {
s string
quoteChar QuoteChar
expected string
}{
{s: "", quoteChar: QuoteCharDoubleQuote, expected: ""},
{s: "abc", quoteChar: QuoteCharDoubleQuote, expected: "abc"},
{s: "ab\"c", quoteChar: QuoteCharDoubleQuote, expected: "ab&quot;c"},
{s: "ab\tc", quoteChar: QuoteCharDoubleQuote, expected: "ab&#x9;c"},
{s: "ab\nc", quoteChar: QuoteCharDoubleQuote, expected: "ab&#xA;c"},
{s: "ab'c", quoteChar: QuoteCharDoubleQuote, expected: "ab'c"},
{s: "ab'c", quoteChar: QuoteCharSingleQuote, expected: "ab&apos;c"},
{s: "ab\"c", quoteChar: QuoteCharSingleQuote, expected: "ab\"c"},
{s: "ab\u008fc", quoteChar: QuoteCharDoubleQuote, expected: "ab\u008Fc"},
{s: "𝟘𝟙", quoteChar: QuoteCharDoubleQuote, expected: "𝟘𝟙"},
}
for i, rec := range data {
t.Run(fmt.Sprintf("[%d] escapeJsxAttributeString(%q, %v)", i, rec.s, rec.quoteChar), func(t *testing.T) {
t.Parallel()
actual := escapeJsxAttributeString(rec.s, rec.quoteChar)
assert.Equal(t, actual, rec.expected)
})
}
}
func TestIsRecognizedTripleSlashComment(t *testing.T) {
t.Parallel()
data := []struct {
s string
commentRange ast.CommentRange
expected bool
}{
{s: "", commentRange: ast.CommentRange{Kind: ast.KindMultiLineCommentTrivia}, expected: false},
{s: "", commentRange: ast.CommentRange{Kind: ast.KindSingleLineCommentTrivia}, expected: false},
{s: "/a", expected: false},
{s: "//", expected: false},
{s: "//a", expected: false},
{s: "///", expected: false},
{s: "///a", expected: false},
{s: "///<reference path=\"foo\" />", expected: true},
{s: "///<reference types=\"foo\" />", expected: true},
{s: "///<reference lib=\"foo\" />", expected: true},
{s: "///<reference no-default-lib=\"foo\" />", expected: true},
{s: "///<amd-dependency path=\"foo\" />", expected: true},
{s: "///<amd-module />", expected: true},
{s: "/// <reference path=\"foo\" />", expected: true},
{s: "/// <reference types=\"foo\" />", expected: true},
{s: "/// <reference lib=\"foo\" />", expected: true},
{s: "/// <reference no-default-lib=\"foo\" />", expected: true},
{s: "/// <amd-dependency path=\"foo\" />", expected: true},
{s: "/// <amd-module />", expected: true},
{s: "/// <reference path=\"foo\"/>", expected: true},
{s: "/// <reference types=\"foo\"/>", expected: true},
{s: "/// <reference lib=\"foo\"/>", expected: true},
{s: "/// <reference no-default-lib=\"foo\"/>", expected: true},
{s: "/// <amd-dependency path=\"foo\"/>", expected: true},
{s: "/// <amd-module/>", expected: true},
{s: "/// <reference path='foo' />", expected: true},
{s: "/// <reference types='foo' />", expected: true},
{s: "/// <reference lib='foo' />", expected: true},
{s: "/// <reference no-default-lib='foo' />", expected: true},
{s: "/// <amd-dependency path='foo' />", expected: true},
{s: "/// <reference path=\"foo\" /> ", expected: true},
{s: "/// <reference types=\"foo\" /> ", expected: true},
{s: "/// <reference lib=\"foo\" /> ", expected: true},
{s: "/// <reference no-default-lib=\"foo\" /> ", expected: true},
{s: "/// <amd-dependency path=\"foo\" /> ", expected: true},
{s: "/// <amd-module /> ", expected: true},
{s: "/// <foo />", expected: false},
{s: "/// <reference />", expected: false},
{s: "/// <amd-dependency />", expected: false},
}
for i, rec := range data {
t.Run(fmt.Sprintf("[%d] isRecognizedTripleSlashComment()", i), func(t *testing.T) {
t.Parallel()
commentRange := rec.commentRange
if commentRange.Kind == ast.KindUnknown {
commentRange.Kind = ast.KindSingleLineCommentTrivia
commentRange.TextRange = core.NewTextRange(0, len(rec.s))
}
actual := IsRecognizedTripleSlashComment(rec.s, commentRange)
assert.Equal(t, actual, rec.expected)
})
}
}