package encoder import ( "encoding/binary" "errors" "fmt" "github.com/microsoft/typescript-go/internal/ast" "github.com/microsoft/typescript-go/internal/core" "github.com/microsoft/typescript-go/internal/tspath" ) // astDecoder reconstructs real *ast.Node objects from binary-encoded data. type astDecoder struct { raw []byte strTable uint32 strData uint32 extData uint32 nodeOff uint32 nodeCount int factory *ast.NodeFactory childBuf []int // Single Go string covering all string data; substrings are zero-alloc slices. allStringData string // Arena for batch-allocating []*ast.Node slices used by NodeLists. nodeArena []*ast.Node // Results nodes []*ast.Node nodeLists []*ast.NodeList } // DecodeSourceFile decodes binary-encoded data into an *ast.SourceFile. func DecodeSourceFile(data []byte) (*ast.SourceFile, error) { node, err := DecodeNodes(data) if err != nil { return nil, err } if node.Kind != ast.KindSourceFile { return nil, fmt.Errorf("expected SourceFile root, got %v", node.Kind) } return node.AsSourceFile(), nil } // DecodeNodes decodes binary-encoded AST data into a tree of *ast.Node objects. func DecodeNodes(data []byte) (*ast.Node, error) { d, err := newASTDecoder(data) if err != nil { return nil, err } return d.decode() } func newASTDecoder(data []byte) (*astDecoder, error) { if len(data) < HeaderSize { return nil, fmt.Errorf("data too short for header: %d bytes", len(data)) } version := data[HeaderOffsetMetadata+3] if version != ProtocolVersion { return nil, fmt.Errorf("unsupported protocol version %d (expected %d)", version, ProtocolVersion) } strTable := readLE32(data, HeaderOffsetStringOffsets) strData := readLE32(data, HeaderOffsetStringData) extData := readLE32(data, HeaderOffsetExtendedData) nodeOff := readLE32(data, HeaderOffsetNodes) dataLen := uint32(len(data)) // Validate that all offsets are within the buffer. if strTable > dataLen || strData > dataLen || extData > dataLen || nodeOff > dataLen { return nil, fmt.Errorf("invalid AST header offsets: offsets exceed data length (%d)", dataLen) } // Validate monotonic non-decreasing order of regions. if !(strTable <= strData && strData <= extData && extData <= nodeOff) { return nil, fmt.Errorf("invalid AST header offsets: expected strTable <= strData <= extData <= nodeOff (got %d, %d, %d, %d)", strTable, strData, extData, nodeOff) } d := &astDecoder{ raw: data, strTable: strTable, strData: strData, extData: extData, nodeOff: nodeOff, factory: ast.NewNodeFactory(ast.NodeFactoryHooks{}), } d.nodeCount = (len(data) - int(d.nodeOff)) / NodeSize // Convert entire string data region to a single Go string upfront. // Substringing a Go string shares the backing array, so subsequent // getString calls produce substrings with zero allocations. d.allStringData = string(data[d.strData:]) return d, nil } // allocNodeSlice returns a zero-length slice with the given capacity, backed by // the pre-allocated nodeArena. This avoids a heap allocation per NodeList. func (d *astDecoder) allocNodeSlice(capacity int) []*ast.Node { start := len(d.nodeArena) d.nodeArena = d.nodeArena[:start+capacity] return d.nodeArena[start : start : start+capacity] } // nodeField reads a uint32 field from node i at the given field offset. func (d *astDecoder) nodeField(i int, field int) uint32 { return readLE32(d.raw, int(d.nodeOff)+i*NodeSize+field) } func (d *astDecoder) getString(idx uint32) string { offBase := int(d.strTable) + int(idx)*4 start := readLE32(d.raw, offBase) end := readLE32(d.raw, offBase+4) return d.allStringData[start:end] } // collectChildren returns indices of direct children of node i. // The returned slice is reused across calls; callers must not retain it. func (d *astDecoder) collectChildren(i int) []int { d.childBuf = d.childBuf[:0] if i+1 >= d.nodeCount { return d.childBuf } firstChild := i + 1 if d.nodeField(firstChild, NodeOffsetParent) != uint32(i) { return d.childBuf } d.childBuf = append(d.childBuf, firstChild) next := int(d.nodeField(firstChild, NodeOffsetNext)) for next != 0 { d.childBuf = append(d.childBuf, next) next = int(d.nodeField(next, NodeOffsetNext)) } return d.childBuf } func (d *astDecoder) decode() (*ast.Node, error) { if d.nodeCount < 2 { return nil, errors.New("no nodes to decode") } d.nodes = make([]*ast.Node, d.nodeCount) d.nodeLists = make([]*ast.NodeList, d.nodeCount) // Pre-allocate arena for NodeList child slices. Each node can appear as a // child at most once, so nodeCount is an upper bound on total child pointers. d.nodeArena = make([]*ast.Node, 0, d.nodeCount) // Process bottom-up so children exist before parents. for i := d.nodeCount - 1; i >= 1; i-- { kind := d.nodeField(i, NodeOffsetKind) pos := d.nodeField(i, NodeOffsetPos) end := d.nodeField(i, NodeOffsetEnd) data := d.nodeField(i, NodeOffsetData) childIndices := d.collectChildren(i) if kind == SyntaxKindNodeList { childNodes := d.allocNodeSlice(len(childIndices)) for _, ci := range childIndices { if d.nodes[ci] != nil { childNodes = append(childNodes, d.nodes[ci]) } } nl := d.factory.NewNodeList(childNodes) nl.Loc = core.NewTextRange(int(pos), int(end)) d.nodeLists[i] = nl continue } node, err := d.createNode(ast.Kind(kind), data, childIndices) if err != nil { return nil, fmt.Errorf("at node %d (kind %v): %w", i, ast.Kind(kind), err) } node.Loc = core.NewTextRange(int(pos), int(end)) node.Flags = ast.NodeFlags(d.nodeField(i, NodeOffsetFlags)) d.nodes[i] = node } return d.nodes[1], nil } // getModifierList creates a *ast.ModifierList from a child index that is a NodeList. func (d *astDecoder) getModifierList(ci int) *ast.ModifierList { nl := d.nodeLists[ci] if nl == nil { return nil } ml := d.factory.NewModifierList(nl.Nodes) ml.Loc = nl.Loc return ml } // childIterator helps walk through children based on a bitmask. type childIterator struct { indices []int pos int } func newChildIter(indices []int) childIterator { return childIterator{indices: indices} } // next returns the index of the next child, advancing the position. func (it *childIterator) next() int { if it.pos >= len(it.indices) { return 0 } ci := it.indices[it.pos] it.pos++ return ci } // nextIf returns the index of the next child if the corresponding mask bit is set. func (it *childIterator) nextIf(mask uint8, bit uint8) int { if mask&(1<> 24) & 0x3f) switch dataType { case NodeDataTypeString: return d.createStringNode(kind, data, commonData) case NodeDataTypeExtendedData: return d.createExtendedNode(kind, data, childIndices, commonData) default: return d.createChildrenNode(kind, data, childIndices, commonData) } } func (d *astDecoder) decodeExtendedData_SourceFile(data uint32, childIndices []int, commonData uint8) (*ast.Node, error) { extOff := int(d.extData) + int(data&NodeDataStringIndexMask) textIdx := readLE32(d.raw, extOff) fileNameIdx := readLE32(d.raw, extOff+4) pathIdx := readLE32(d.raw, extOff+8) text := d.getString(textIdx) fileName := d.getString(fileNameIdx) path := d.getString(pathIdx) // Recover parse options from header. parseOpts := readLE32(d.raw, HeaderOffsetParseOptions) opts := ast.SourceFileParseOptions{ FileName: fileName, Path: tspath.Path(path), ExternalModuleIndicatorOptions: ast.ExternalModuleIndicatorOptions{ JSX: parseOpts&1 != 0, Force: parseOpts&2 != 0, }, } // Collect children: first is statements NodeList, second is EndOfFile. var stmts *ast.NodeList var endOfFile *ast.Node for _, ci := range childIndices { if d.nodeField(ci, NodeOffsetKind) == SyntaxKindNodeList { stmts = d.nodeListAt(ci) } else if d.nodes[ci] != nil && d.nodes[ci].Kind == ast.KindEndOfFile { endOfFile = d.nodes[ci] } } if endOfFile == nil { endOfFile = d.factory.NewToken(ast.KindEndOfFile) } return d.factory.NewSourceFile(opts, text, stmts, endOfFile), nil } func (d *astDecoder) decodeExtendedData_TemplateHead(data uint32, childIndices []int, commonData uint8) (*ast.Node, error) { extOff := int(d.extData) + int(data&NodeDataStringIndexMask) textIdx := readLE32(d.raw, extOff) rawTextIdx := readLE32(d.raw, extOff+4) flags := readLE32(d.raw, extOff+8) return d.factory.NewTemplateHead(d.getString(textIdx), d.getString(rawTextIdx), ast.TokenFlags(flags)), nil } func (d *astDecoder) decodeExtendedData_TemplateMiddle(data uint32, childIndices []int, commonData uint8) (*ast.Node, error) { extOff := int(d.extData) + int(data&NodeDataStringIndexMask) textIdx := readLE32(d.raw, extOff) rawTextIdx := readLE32(d.raw, extOff+4) flags := readLE32(d.raw, extOff+8) return d.factory.NewTemplateMiddle(d.getString(textIdx), d.getString(rawTextIdx), ast.TokenFlags(flags)), nil } func (d *astDecoder) decodeExtendedData_TemplateTail(data uint32, childIndices []int, commonData uint8) (*ast.Node, error) { extOff := int(d.extData) + int(data&NodeDataStringIndexMask) textIdx := readLE32(d.raw, extOff) rawTextIdx := readLE32(d.raw, extOff+4) flags := readLE32(d.raw, extOff+8) return d.factory.NewTemplateTail(d.getString(textIdx), d.getString(rawTextIdx), ast.TokenFlags(flags)), nil } func (d *astDecoder) singleChild(childIndices []int) *ast.Node { if len(childIndices) == 0 { return nil } return d.nodes[childIndices[0]] } func (d *astDecoder) singleNodeListChild(childIndices []int) *ast.NodeList { if len(childIndices) == 0 { return nil } return d.nodeLists[childIndices[0]] } func readLE32(data []byte, offset int) uint32 { if offset < 0 || offset+4 > len(data) { return 0 } return binary.LittleEndian.Uint32(data[offset : offset+4]) } // Hand-written commonData decoding functions. Each extracts the original values // from the 6-bit commonData that were packed by the corresponding // getNodeCommonData_* function. func decodeNodeCommonData_SyntheticExpression(_ uint8) (any, bool) { panic("SyntheticExpression should never be decoded") } // Hand-written extended data decoding functions for literal nodes. func (d *astDecoder) decodeExtendedData_StringLiteral(data uint32, _ []int, _ uint8) (*ast.Node, error) { extOff := int(d.extData) + int(data&NodeDataStringIndexMask) textIdx := readLE32(d.raw, extOff) flags := readLE32(d.raw, extOff+4) return d.factory.NewStringLiteral(d.getString(textIdx), ast.TokenFlags(flags)), nil } func (d *astDecoder) decodeExtendedData_NumericLiteral(data uint32, _ []int, _ uint8) (*ast.Node, error) { extOff := int(d.extData) + int(data&NodeDataStringIndexMask) textIdx := readLE32(d.raw, extOff) flags := readLE32(d.raw, extOff+4) return d.factory.NewNumericLiteral(d.getString(textIdx), ast.TokenFlags(flags)), nil } func (d *astDecoder) decodeExtendedData_BigIntLiteral(data uint32, _ []int, _ uint8) (*ast.Node, error) { extOff := int(d.extData) + int(data&NodeDataStringIndexMask) textIdx := readLE32(d.raw, extOff) flags := readLE32(d.raw, extOff+4) return d.factory.NewBigIntLiteral(d.getString(textIdx), ast.TokenFlags(flags)), nil } func (d *astDecoder) decodeExtendedData_RegularExpressionLiteral(data uint32, _ []int, _ uint8) (*ast.Node, error) { extOff := int(d.extData) + int(data&NodeDataStringIndexMask) textIdx := readLE32(d.raw, extOff) flags := readLE32(d.raw, extOff+4) return d.factory.NewRegularExpressionLiteral(d.getString(textIdx), ast.TokenFlags(flags)), nil } func (d *astDecoder) decodeExtendedData_NoSubstitutionTemplateLiteral(data uint32, _ []int, _ uint8) (*ast.Node, error) { extOff := int(d.extData) + int(data&NodeDataStringIndexMask) textIdx := readLE32(d.raw, extOff) flags := readLE32(d.raw, extOff+4) return d.factory.NewNoSubstitutionTemplateLiteral(d.getString(textIdx), ast.TokenFlags(flags)), nil }