Files
kjol/tools/tsgo/internal/printer/changetrackerwriter.go
2026-07-09 16:50:43 -04:00

248 lines
7.5 KiB
Go

package printer
import (
"unicode/utf8"
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/scanner"
"github.com/microsoft/typescript-go/internal/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, indentSize int) *ChangeTrackerWriter {
// TODO: Callers passing -1 should pass actual indent options once indent-related formatting is ported.
if indentSize < 0 {
indentSize = defaultIndentSize
}
ctw := &ChangeTrackerWriter{
textWriter: textWriter{newLine: newline, indentSize: indentSize},
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()
// trim trailing whitespaces
pos := len(s)
for pos > 0 {
r, size := utf8.DecodeLastRuneInString(s[:pos])
if stringutil.IsWhiteSpaceLike(r) {
pos -= size
} else {
break
}
}
ct.lastNonTriviaPosition -= len(s) - pos
}
}
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 {
newNodeList := ct.assignPositionsToNodeArray(&modifiers.NodeList, v)
// Return a new ModifierList so that VisitEachChild/Update detects the
// change and creates a new node with reassigned child positions.
return factory.NewModifierList(newNodeList.Nodes)
}
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() core.UTF16Offset { 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()
}