vendor tsgo

This commit is contained in:
2026-07-09 16:50:43 -04:00
parent c06ea2e5a4
commit 98978e4930
5804 changed files with 1556156 additions and 101 deletions

View File

@@ -0,0 +1,381 @@
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<<bit) == 0 {
return 0
}
return it.next()
}
func (d *astDecoder) nodeAt(ci int) *ast.Node {
if ci == 0 {
return nil
}
return d.nodes[ci]
}
func (d *astDecoder) nodeListAt(ci int) *ast.NodeList {
if ci == 0 {
return nil
}
return d.nodeLists[ci]
}
func (d *astDecoder) modifierListAt(ci int) *ast.ModifierList {
if ci == 0 {
return nil
}
return d.getModifierList(ci)
}
func (d *astDecoder) createNode(kind ast.Kind, data uint32, childIndices []int) (*ast.Node, error) {
dataType := data & NodeDataTypeMask
commonData := uint8((data >> 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
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,450 @@
package encoder_test
import (
"os"
"path/filepath"
"testing"
"github.com/microsoft/typescript-go/internal/api/encoder"
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/parser"
"github.com/microsoft/typescript-go/internal/repo"
"gotest.tools/v3/assert"
)
func parseSourceFile(code string) *ast.SourceFile {
return parser.ParseSourceFile(ast.SourceFileParseOptions{
FileName: "/test.ts",
Path: "/test.ts",
}, code, core.ScriptKindTS)
}
func TestDecodeSourceFile_Basic(t *testing.T) {
t.Parallel()
sf := parseSourceFile("let x = 1;")
buf, _, err := encoder.EncodeSourceFile(sf)
assert.NilError(t, err)
decoded, err := encoder.DecodeSourceFile(buf)
assert.NilError(t, err)
assert.Equal(t, decoded.AsNode().Kind, ast.KindSourceFile)
assert.Equal(t, decoded.FileName(), "/test.ts")
assert.Equal(t, decoded.Text(), "let x = 1;")
assert.Assert(t, decoded.Statements != nil)
assert.Assert(t, decoded.EndOfFileToken != nil)
}
func TestDecodeSourceFile_Statements(t *testing.T) {
t.Parallel()
sf := parseSourceFile("let a = 1;\nlet b = 2;\nlet c = 3;")
buf, _, err := encoder.EncodeSourceFile(sf)
assert.NilError(t, err)
decoded, err := encoder.DecodeSourceFile(buf)
assert.NilError(t, err)
assert.Equal(t, len(decoded.Statements.Nodes), 3)
for i, stmt := range decoded.Statements.Nodes {
assert.Equal(t, stmt.Kind, ast.KindVariableStatement, "statement %d", i)
}
}
func TestDecodeSourceFile_VariableDeclaration(t *testing.T) {
t.Parallel()
sf := parseSourceFile("let x = 1;")
buf, _, err := encoder.EncodeSourceFile(sf)
assert.NilError(t, err)
decoded, err := encoder.DecodeSourceFile(buf)
assert.NilError(t, err)
varStmt := decoded.Statements.Nodes[0].AsVariableStatement()
assert.Assert(t, varStmt.DeclarationList != nil)
declList := varStmt.DeclarationList.AsVariableDeclarationList()
assert.Assert(t, declList.Declarations != nil)
assert.Equal(t, len(declList.Declarations.Nodes), 1)
decl := declList.Declarations.Nodes[0].AsVariableDeclaration()
assert.Equal(t, decl.Name().Kind, ast.KindIdentifier)
assert.Equal(t, decl.Name().AsIdentifier().Text, "x")
assert.Assert(t, decl.Initializer != nil)
assert.Equal(t, decl.Initializer.Kind, ast.KindNumericLiteral)
assert.Equal(t, decl.Initializer.AsNumericLiteral().Text, "1")
}
func TestDecodeSourceFile_VariableDeclarationListFlags(t *testing.T) {
t.Parallel()
tests := []struct {
name string
code string
expected ast.NodeFlags
}{
{"const", "const x = 1;", ast.NodeFlagsConst},
{"let", "let x = 1;", ast.NodeFlagsLet},
{"var", "var x = 1;", ast.NodeFlagsNone},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
sf := parseSourceFile(tt.code)
buf, _, err := encoder.EncodeSourceFile(sf)
assert.NilError(t, err)
decoded, err := encoder.DecodeSourceFile(buf)
assert.NilError(t, err)
declList := decoded.Statements.Nodes[0].AsVariableStatement().DeclarationList.AsVariableDeclarationList()
got := declList.Flags & (ast.NodeFlagsLet | ast.NodeFlagsConst)
assert.Equal(t, got, tt.expected, "flags for %q: got %d, want %d", tt.code, got, tt.expected)
})
}
}
func TestDecodeSourceFile_FunctionDeclaration(t *testing.T) {
t.Parallel()
sf := parseSourceFile("function add(a: number, b: number): number { return a + b; }")
buf, _, err := encoder.EncodeSourceFile(sf)
assert.NilError(t, err)
decoded, err := encoder.DecodeSourceFile(buf)
assert.NilError(t, err)
funcDecl := decoded.Statements.Nodes[0].AsFunctionDeclaration()
assert.Assert(t, funcDecl.Name() != nil)
assert.Equal(t, funcDecl.Name().AsIdentifier().Text, "add")
assert.Assert(t, funcDecl.Parameters != nil)
assert.Equal(t, len(funcDecl.Parameters.Nodes), 2)
assert.Assert(t, funcDecl.Type != nil)
assert.Assert(t, funcDecl.Body != nil)
param0 := funcDecl.Parameters.Nodes[0].AsParameterDeclaration()
assert.Equal(t, param0.Name().AsIdentifier().Text, "a")
assert.Assert(t, param0.Type != nil)
}
func TestDecodeSourceFile_ImportDeclaration(t *testing.T) {
t.Parallel()
sf := parseSourceFile(`import { bar } from "bar";`)
buf, _, err := encoder.EncodeSourceFile(sf)
assert.NilError(t, err)
decoded, err := encoder.DecodeSourceFile(buf)
assert.NilError(t, err)
imp := decoded.Statements.Nodes[0].AsImportDeclaration()
assert.Assert(t, imp.ImportClause != nil)
assert.Assert(t, imp.ModuleSpecifier != nil)
assert.Equal(t, imp.ModuleSpecifier.AsStringLiteral().Text, "bar")
clause := imp.ImportClause.AsImportClause()
assert.Assert(t, clause.NamedBindings != nil)
namedImports := clause.NamedBindings.AsNamedImports()
assert.Assert(t, namedImports.Elements != nil)
assert.Equal(t, len(namedImports.Elements.Nodes), 1)
spec := namedImports.Elements.Nodes[0].AsImportSpecifier()
assert.Equal(t, spec.Name().AsIdentifier().Text, "bar")
}
func TestDecodeSourceFile_IfStatement(t *testing.T) {
t.Parallel()
sf := parseSourceFile("if (true) { } else { }")
buf, _, err := encoder.EncodeSourceFile(sf)
assert.NilError(t, err)
decoded, err := encoder.DecodeSourceFile(buf)
assert.NilError(t, err)
ifStmt := decoded.Statements.Nodes[0].AsIfStatement()
assert.Assert(t, ifStmt.Expression != nil)
assert.Assert(t, ifStmt.ThenStatement != nil)
assert.Assert(t, ifStmt.ElseStatement != nil)
assert.Equal(t, ifStmt.ThenStatement.Kind, ast.KindBlock)
assert.Equal(t, ifStmt.ElseStatement.Kind, ast.KindBlock)
}
func TestDecodeSourceFile_TemplateExpression(t *testing.T) {
t.Parallel()
sf := parseSourceFile("let x = `hello ${name} world`;")
buf, _, err := encoder.EncodeSourceFile(sf)
assert.NilError(t, err)
decoded, err := encoder.DecodeSourceFile(buf)
assert.NilError(t, err)
varDecl := decoded.Statements.Nodes[0].AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes[0].AsVariableDeclaration()
tmplExpr := varDecl.Initializer.AsTemplateExpression()
assert.Assert(t, tmplExpr.Head != nil)
assert.Equal(t, tmplExpr.Head.AsTemplateHead().Text, "hello ")
assert.Assert(t, tmplExpr.TemplateSpans != nil)
assert.Equal(t, len(tmplExpr.TemplateSpans.Nodes), 1)
span := tmplExpr.TemplateSpans.Nodes[0].AsTemplateSpan()
assert.Assert(t, span.Expression != nil)
assert.Equal(t, span.Expression.Kind, ast.KindIdentifier)
assert.Assert(t, span.Literal != nil)
assert.Equal(t, span.Literal.AsTemplateTail().Text, " world")
}
func TestDecodeSourceFile_ExportModifier(t *testing.T) {
t.Parallel()
sf := parseSourceFile("export function foo() {}")
buf, _, err := encoder.EncodeSourceFile(sf)
assert.NilError(t, err)
decoded, err := encoder.DecodeSourceFile(buf)
assert.NilError(t, err)
funcDecl := decoded.Statements.Nodes[0].AsFunctionDeclaration()
assert.Assert(t, funcDecl.Modifiers() != nil)
assert.Equal(t, len(funcDecl.Modifiers().Nodes), 1)
assert.Equal(t, funcDecl.Modifiers().Nodes[0].Kind, ast.KindExportKeyword)
}
func TestDecodeSourceFile_Positions(t *testing.T) {
t.Parallel()
code := "let x = 1;"
sf := parseSourceFile(code)
buf, _, err := encoder.EncodeSourceFile(sf)
assert.NilError(t, err)
decoded, err := encoder.DecodeSourceFile(buf)
assert.NilError(t, err)
assert.Equal(t, decoded.AsNode().Pos(), 0)
assert.Equal(t, decoded.AsNode().End(), len(code))
}
func TestDecodeSourceFile_ClassDeclaration(t *testing.T) {
t.Parallel()
sf := parseSourceFile("class Foo { bar(): void {} }")
buf, _, err := encoder.EncodeSourceFile(sf)
assert.NilError(t, err)
decoded, err := encoder.DecodeSourceFile(buf)
assert.NilError(t, err)
classDecl := decoded.Statements.Nodes[0].AsClassDeclaration()
assert.Assert(t, classDecl.Name() != nil)
assert.Equal(t, classDecl.Name().AsIdentifier().Text, "Foo")
assert.Assert(t, classDecl.Members != nil)
assert.Equal(t, len(classDecl.Members.Nodes), 1)
assert.Equal(t, classDecl.Members.Nodes[0].Kind, ast.KindMethodDeclaration)
}
func TestDecodeNodes_SubtreeRoundTrip(t *testing.T) {
t.Parallel()
sf := parseSourceFile("function greet(name: string) { return `Hello, ${name}!`; }")
var funcNode *ast.Node
visitor := &ast.NodeVisitor{}
visitor.Visit = func(node *ast.Node) *ast.Node {
if node.Kind == ast.KindFunctionDeclaration && funcNode == nil {
funcNode = node
}
return node
}
visitor.VisitEachChild(sf.AsNode())
assert.Assert(t, funcNode != nil)
buf, _, err := encoder.EncodeNode(funcNode, sf)
assert.NilError(t, err)
decoded, err := encoder.DecodeNodes(buf)
assert.NilError(t, err)
assert.Equal(t, decoded.Kind, ast.KindFunctionDeclaration)
funcDecl := decoded.AsFunctionDeclaration()
assert.Assert(t, funcDecl.Name() != nil)
assert.Equal(t, funcDecl.Name().AsIdentifier().Text, "greet")
assert.Assert(t, funcDecl.Parameters != nil)
assert.Equal(t, len(funcDecl.Parameters.Nodes), 1)
assert.Assert(t, funcDecl.Body != nil)
}
func TestDecodeSourceFile_BinaryExpression(t *testing.T) {
t.Parallel()
sf := parseSourceFile("let x = 1 + 2;")
buf, _, err := encoder.EncodeSourceFile(sf)
assert.NilError(t, err)
decoded, err := encoder.DecodeSourceFile(buf)
assert.NilError(t, err)
decl := decoded.Statements.Nodes[0].AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes[0].AsVariableDeclaration()
binExpr := decl.Initializer.AsBinaryExpression()
assert.Assert(t, binExpr.Left != nil)
assert.Assert(t, binExpr.Right != nil)
assert.Assert(t, binExpr.OperatorToken != nil)
assert.Equal(t, binExpr.Left.Kind, ast.KindNumericLiteral)
assert.Equal(t, binExpr.Right.Kind, ast.KindNumericLiteral)
}
func TestDecodeSourceFile_KeywordExpressions(t *testing.T) {
t.Parallel()
// "this" must decode as KeywordExpression, not Token, or the printer panics
sf := parseSourceFile("const x = this;")
buf, _, err := encoder.EncodeSourceFile(sf)
assert.NilError(t, err)
decoded, err := encoder.DecodeSourceFile(buf)
assert.NilError(t, err)
// Navigate: const x = this -> VariableStatement -> declaration -> initializer
decl := decoded.Statements.Nodes[0].AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes[0].AsVariableDeclaration()
thisExpr := decl.Initializer
assert.Equal(t, thisExpr.Kind, ast.KindThisKeyword)
// This would panic if decoded as Token instead of KeywordExpression
assert.Assert(t, thisExpr.AsKeywordExpression() != nil)
}
func TestDecodeSourceFile_EmptyModuleBlock(t *testing.T) {
t.Parallel()
sf := parseSourceFile("namespace N { }")
buf, _, err := encoder.EncodeSourceFile(sf)
assert.NilError(t, err)
decoded, err := encoder.DecodeSourceFile(buf)
assert.NilError(t, err)
// Navigate: namespace N { } -> ModuleDeclaration -> ModuleBlock
mod := decoded.Statements.Nodes[0].AsModuleDeclaration()
assert.Assert(t, mod.Body != nil)
block := mod.Body.AsModuleBlock()
// Statements must be non-nil even when empty, otherwise the printer panics
assert.Assert(t, block.Statements != nil)
assert.Equal(t, len(block.Statements.Nodes), 0)
}
func TestDecodeSourceFile_EmptyBlockAndParams(t *testing.T) {
t.Parallel()
// Empty blocks and parameter lists must decode with non-nil NodeLists (not nil),
// matching parser behavior. Previously the decoder left them nil, crashing the printer.
sf := parseSourceFile("function foo() {}")
buf, _, err := encoder.EncodeSourceFile(sf)
assert.NilError(t, err)
decoded, err := encoder.DecodeSourceFile(buf)
assert.NilError(t, err)
funcDecl := decoded.Statements.Nodes[0].AsFunctionDeclaration()
assert.Assert(t, funcDecl.Parameters != nil, "FunctionDeclaration.Parameters must be non-nil for foo()")
assert.Equal(t, len(funcDecl.Parameters.Nodes), 0)
assert.Assert(t, funcDecl.Body != nil)
block := funcDecl.Body.AsBlock()
assert.Assert(t, block.Statements != nil, "Block.Statements must be non-nil for empty blocks")
assert.Equal(t, len(block.Statements.Nodes), 0)
}
func TestDecodeSourceFile_ArrowFunctionEmptyParams(t *testing.T) {
t.Parallel()
// `() => {}` must decode with non-nil Parameters (empty NodeList),
// matching parser behavior. Previously the decoder left it nil, crashing the printer.
sf := parseSourceFile("const f = () => {};")
buf, _, err := encoder.EncodeSourceFile(sf)
assert.NilError(t, err)
decoded, err := encoder.DecodeSourceFile(buf)
assert.NilError(t, err)
decl := decoded.Statements.Nodes[0].AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes[0].AsVariableDeclaration()
arrow := decl.Initializer.AsArrowFunction()
assert.Assert(t, arrow.Parameters != nil, "ArrowFunction.Parameters must be non-nil for () => {}")
assert.Equal(t, len(arrow.Parameters.Nodes), 0)
assert.Assert(t, arrow.Body != nil)
block := arrow.Body.AsBlock()
assert.Assert(t, block.Statements != nil, "Block.Statements must be non-nil for empty body")
assert.Equal(t, len(block.Statements.Nodes), 0)
}
func TestDecodeSourceFile_FunctionExpressionEmptyParams(t *testing.T) {
t.Parallel()
// `function() {}` must decode with non-nil Parameters (empty NodeList).
sf := parseSourceFile("const f = function() {};")
buf, _, err := encoder.EncodeSourceFile(sf)
assert.NilError(t, err)
decoded, err := encoder.DecodeSourceFile(buf)
assert.NilError(t, err)
decl := decoded.Statements.Nodes[0].AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes[0].AsVariableDeclaration()
funcExpr := decl.Initializer.AsFunctionExpression()
assert.Assert(t, funcExpr.Parameters != nil, "FunctionExpression.Parameters must be non-nil for function() {}")
assert.Equal(t, len(funcExpr.Parameters.Nodes), 0)
}
func TestDecodeSourceFile_PostfixUnaryOperator(t *testing.T) {
t.Parallel()
sf := parseSourceFile("let i = 0; i++;")
buf, _, err := encoder.EncodeSourceFile(sf)
assert.NilError(t, err)
decoded, err := encoder.DecodeSourceFile(buf)
assert.NilError(t, err)
exprStmt := decoded.Statements.Nodes[1].AsExpressionStatement()
postfix := exprStmt.Expression.AsPostfixUnaryExpression()
assert.Equal(t, postfix.Operator, ast.KindPlusPlusToken)
assert.Equal(t, postfix.Operand.Kind, ast.KindIdentifier)
}
func TestDecodeSourceFile_PrefixUnaryOperator(t *testing.T) {
t.Parallel()
sf := parseSourceFile("let x = true; !x;")
buf, _, err := encoder.EncodeSourceFile(sf)
assert.NilError(t, err)
decoded, err := encoder.DecodeSourceFile(buf)
assert.NilError(t, err)
exprStmt := decoded.Statements.Nodes[1].AsExpressionStatement()
prefix := exprStmt.Expression.AsPrefixUnaryExpression()
assert.Equal(t, prefix.Operator, ast.KindExclamationToken)
assert.Equal(t, prefix.Operand.Kind, ast.KindIdentifier)
}
func TestDecodeSourceFile_PostfixDecrement(t *testing.T) {
t.Parallel()
sf := parseSourceFile("let n = 5; n--;")
buf, _, err := encoder.EncodeSourceFile(sf)
assert.NilError(t, err)
decoded, err := encoder.DecodeSourceFile(buf)
assert.NilError(t, err)
exprStmt := decoded.Statements.Nodes[1].AsExpressionStatement()
postfix := exprStmt.Expression.AsPostfixUnaryExpression()
assert.Equal(t, postfix.Operator, ast.KindMinusMinusToken)
}
func BenchmarkDecodeSourceFile(b *testing.B) {
repo.SkipIfNoTypeScriptSubmodule(b)
filePath := filepath.Join(repo.TypeScriptSubmodulePath(), "src/compiler/checker.ts")
fileContent, err := os.ReadFile(filePath)
assert.NilError(b, err)
code := string(fileContent)
sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{
FileName: "/checker.ts",
Path: "/checker.ts",
}, code, core.ScriptKindTS)
buf, _, err := encoder.EncodeSourceFile(sourceFile)
assert.NilError(b, err)
b.Run("parse", func(b *testing.B) {
for b.Loop() {
parser.ParseSourceFile(ast.SourceFileParseOptions{
FileName: "/checker.ts",
Path: "/checker.ts",
}, code, core.ScriptKindTS)
}
})
b.Run("decode", func(b *testing.B) {
for b.Loop() {
_, decodeErr := encoder.DecodeSourceFile(buf)
assert.NilError(b, decodeErr)
}
})
}

View File

@@ -0,0 +1,844 @@
package encoder
import (
"cmp"
"encoding/binary"
"fmt"
"slices"
"sync"
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/core"
"github.com/zeebo/xxh3"
)
func init() {
if ast.KindLastUnaryOperator > 0x3f {
panic(fmt.Sprintf("KindLastUnaryOperator (%d) exceeds the 6-bit commonData capacity (max 63)", ast.KindLastUnaryOperator))
}
}
const (
NodeOffsetKind = iota * 4
NodeOffsetPos
NodeOffsetEnd
NodeOffsetNext
NodeOffsetParent
NodeOffsetData
NodeOffsetFlags
// NodeSize is the number of bytes that represents a single node in the encoded format.
NodeSize
)
const (
NodeDataTypeChildren uint32 = iota << 30
NodeDataTypeString
NodeDataTypeExtendedData
)
const (
NodeDataTypeMask uint32 = 0xc0_00_00_00
NodeDataChildMask uint32 = 0x00_00_00_ff
NodeDataStringIndexMask uint32 = 0x00_ff_ff_ff
)
const (
SyntaxKindNodeList uint32 = 1<<32 - 1
)
const (
HeaderOffsetMetadata = iota * 4
HeaderOffsetHashLo0
HeaderOffsetHashLo1
HeaderOffsetHashHi0
HeaderOffsetHashHi1
HeaderOffsetParseOptions
HeaderOffsetStringOffsets
HeaderOffsetStringData
HeaderOffsetExtendedData
HeaderOffsetStructuredData
HeaderOffsetNodes
HeaderSize
)
const (
ProtocolVersion uint8 = 5
)
// Source File Binary Format
// =========================
//
// The following defines a protocol for serializing TypeScript SourceFile objects to a compact binary format. All integer
// values are little-endian.
//
// Overview
// --------
//
// The format comprises seven sections:
//
// | Section | Length | Description |
// | ------------------ | ------------------ | ----------------------------------------------------------------------------------------------- |
// | Header | 44 bytes | Contains the content hash, parse options, flags, and byte offsets to the start of each section. |
// | String offsets | 8 bytes per string | Pairs of starting byte offsets and ending byte offsets into the **string data** section. |
// | String data | variable | UTF-8 encoded string data. |
// | Extended node data | variable | Extra data for some kinds of nodes. |
// | Structured data | variable | Msgpack-encoded metadata blobs (e.g. file references). |
// | Nodes | 28 bytes per node | Defines the AST structure of the file, with references to strings and extended data. |
//
// Header (44 bytes)
// -----------------
//
// The header contains the following fields:
//
// | Byte offset | Type | Field |
// | ----------- | --------- | ------------------------------------------------- |
// | 0 | uint8 | Protocol version |
// | 1-3 | | Reserved |
// | 4-19 | uint128 | Source file content hash (xxh3, LE) |
// | 20-23 | uint32 | Parse options (bitmask; bit 0: JSX, bit 1: Force) |
// | 24-27 | uint32 | Byte offset to string offsets section |
// | 28-31 | uint32 | Byte offset to string data section |
// | 32-35 | uint32 | Byte offset to extended node data section |
// | 36-39 | uint32 | Byte offset to structured data section |
// | 40-43 | uint32 | Byte offset to nodes section |
//
// String offsets (8 bytes per string)
// -----------------------------------
//
// Each string offset entry consists of two 4-byte unsigned integers, representing the start and end byte offsets into the
// **string data** section.
//
// String data (variable)
// ----------------------
//
// The string data section contains UTF-8 encoded string data, with WTF-8 used for JS strings containing lone UTF-16
// surrogates. In typical cases, the entirety of the string data is the source file text, and individual nodes with
// string properties reference their positional slice of the file text. In cases where a node's string property is not
// equal to the slice of file text at its position, the unique string is appended to the string data section after the
// file text.
//
// Extended node data (variable)
// -----------------------------
//
// The extended node data section contains additional data for specific node types. The length and meaning of each entry
// is defined by the node type.
//
// Currently, the only node types that use this section are `TemplateHead`, `TemplateMiddle`, `TemplateTail`, and
// `SourceFile`. The extended data format for the first three is:
//
// | Byte offset | Type | Field |
// | ----------- | ------ | ------------------------------------------------ |
// | 0-4 | uint32 | Index of `text` in the string offsets section |
// | 4-8 | uint32 | Index of `rawText` in the string offsets section |
// | 8-12 | uint32 | Value of `templateFlags` |
//
// and for `SourceFile` is:
//
// | Byte offset | Type | Field |
// | ----------- | ------ | -------------------------------------------------------------- |
// | 0-4 | uint32 | Index of `text` in the string offsets section |
// | 4-8 | uint32 | Index of `fileName` in the string offsets section |
// | 8-12 | uint32 | Index of `path` in the string offsets section |
// | 12-16 | uint32 | Value of `languageVariant` |
// | 16-20 | uint32 | Value of `scriptKind` |
// | 20-24 | uint32 | Byte offset of `referencedFiles` in structured data section |
// | 24-28 | uint32 | Byte offset of `typeReferenceDirectives` in structured data |
// | 28-32 | uint32 | Byte offset of `libReferenceDirectives` in structured data |
// | 32-36 | uint32 | Byte offset of `imports` node index array in structured data |
// | 36-40 | uint32 | Byte offset of `moduleAugmentations` node index array |
// | 40-44 | uint32 | Byte offset of `ambientModuleNames` string array |
// | 44-48 | uint32 | Node index of `externalModuleIndicator` (0 = nil) |
//
// Structured data (variable)
// --------------------------
//
// The structured data section contains msgpack-encoded metadata blobs. Each blob is a self-contained
// msgpack value. File reference arrays use the following tuple format:
//
// [pos: uint, end: uint, fileName: string, resolutionMode: uint, preserve: bool]
//
// Node index arrays (imports, moduleAugmentations) are msgpack arrays of uint values, where each
// value is a node index into the nodes section. String arrays (ambientModuleNames) are msgpack
// arrays of string values.
//
// An offset of 0xFFFFFFFF indicates no data (empty array).
//
// Nodes (28 bytes per node)
// -------------------------
//
// The nodes section contains the AST structure of the file. Nodes are represented in a flat array in source order,
// heavily inspired by https://marvinh.dev/blog/speeding-up-javascript-ecosystem-part-11/. Each node has the following
// structure:
//
// | Byte offset | Type | Field |
// | ----------- | ------ | -------------------------- |
// | 0-4 | uint32 | Kind |
// | 4-8 | uint32 | Pos |
// | 8-12 | uint32 | End |
// | 12-16 | uint32 | Node index of next sibling |
// | 16-20 | uint32 | Node index of parent |
// | 20-24 | | Node data |
// | 24-28 | uint32 | Node flags |
//
// The first 28 bytes of the nodes section are zeros representing a nil node, such that nodes without a parent or next
// sibling can unambiuously use `0` for those indices.
//
// NodeLists are represented as normal nodes with the special `kind` value `0xff_ff_ff_ff`. They are considered the parent
// of their contents in the encoded format. A client reconstructing an AST similar to TypeScript's internal representation
// should instead set the `parent` pointers of a NodeList's children to the NodeList's parent. A NodeList's `data` field
// is the uint32 length of the list, and does not use one of the data types described below.
//
// For node types other than NodeList, the node data field encodes one of the following, determined by the first 2 bits of
// the field:
//
// | Value | Data type | Description |
// | ----- | --------- | ------------------------------------------------------------------------------------ |
// | 0b00 | Children | Disambiguates which named properties of the node its children should be assigned to. |
// | 0b01 | String | The index of the node's string property in the **string offsets** section. |
// | 0b10 | Extended | The byte offset of the node's extended data into the **extended node data** section. |
// | 0b11 | Reserved | Reserved for future use. |
//
// In all node data types, the remaining 6 bits of the first byte are used to encode small values specific to the node
// type. For most node types, these are individual boolean flags. For unary expressions, all 6 bits encode the operator's
// SyntaxKind value (e.g., PlusPlusToken=45, TildeToken=54), which fits because KindLastUnaryOperator (54) <= 0x3f (63).
//
// | Node type | Bits 0-5 | Notes |
// | ---------------------------- | ------------------------------------- | ------------------------------ |
// | `ImportSpecifier` | Bit 0: `isTypeOnly` | |
// | `ImportClause` | Bit 0: `isTypeOnly`, Bit 1: `isDefer` | |
// | `ExportSpecifier` | Bit 0: `isTypeOnly` | |
// | `ImportEqualsDeclaration` | Bit 0: `isTypeOnly` | |
// | `ExportDeclaration` | Bit 0: `isTypeOnly` | |
// | `ImportTypeNode` | Bit 0: `isTypeOf` | |
// | `ExportAssignment` | Bit 0: `isExportEquals` | |
// | `Block` | Bit 0: `multiline` | |
// | `ArrayLiteralExpression` | Bit 0: `multiline` | |
// | `ObjectLiteralExpression` | Bit 0: `multiline` | |
// | `JsxText` | Bit 0: `containsOnlyTriviaWhiteSpaces`| |
// | `JSDocTypeLiteral` | Bit 0: `isArrayType` | |
// | `JsDocPropertyTag` | Bit 0: `isBracketed`, Bit 1: `isNameFirst` | |
// | `JsDocParameterTag` | Bit 0: `isBracketed`, Bit 1: `isNameFirst` | |
// | `VariableDeclarationList` | Bit 0: is `let`, Bit 1: is `const` | |
// | `ImportAttributes` | Bit 0: `multiline`, Bit 1: is `assert`| |
// | `PrefixUnaryExpression` | Bits 0-5: operator SyntaxKind | e.g., `!`, `~`, `++`, `--` |
// | `PostfixUnaryExpression` | Bits 0-5: operator SyntaxKind | e.g., `++`, `--` |
//
// The remaining 3 bytes of the node data field vary by data type:
//
// ### Children (0b00)
//
// If a node has fewer children than its type allows, additional data is needed to determine which properties the children
// correspond to. The last byte of the 4-byte data field is a bitmask representing the child properties of the node type,
// in visitor order, where `1` indicates that the child at that property is present and `0` indicates that the property is
// nil. For example, a `MethodDeclaration` has the following child properties:
//
// | Property name | Bit position |
// | -------------- | ------------ |
// | modifiers | 0 |
// | asteriskToken | 1 |
// | name | 2 |
// | postfixToken | 3 |
// | typeParameters | 4 |
// | parameters | 5 |
// | returnType | 6 |
// | body | 7 |
//
// A bitmask with value `0b01100101` would indicate that the next four direct descendants (i.e., node records that have a
// `parent` set to the node index of the `MethodDeclaration`) of the node are its `modifiers`, `name`, `parameters`, and
// `body` properties, in that order. The remaining properties are nil. (To reconstruct the node with named properties, the
// client must consult a static table of each node type's child property names.)
//
// The bitmask may be zero for node types that can only have a single child, since no disambiguation is needed.
// Additionally, the children data type may be used for nodes that can never have children, but do not require other
// data types.
//
// ### String (0b01)
//
// The string data type is used for nodes with a single string property. (Currently, the name of that property is always
// `text`.) The last three bytes of the 4-byte data field form a single 24-bit unsigned integer (i.e.,
// `uint32(0x00_ff_ff_ff & node.data)`) _N_ that is an index into the **string offsets** section. The *N*th 32-bit
// unsigned integer in the **string offsets** section is the byte offset of the start of the string in the **string data**
// section, and the *N+1*th 32-bit unsigned integer is the byte offset of the end of the string in the
// **string data** section.
//
// ### Extended (0b10)
//
// The extended data type is used for nodes with properties that don't fit into either the children or string data types.
// The last three bytes of the 4-byte data field form a single 24-bit unsigned integer (i.e.,
// `uint32(0x00_ff_ff_ff & node.data)`) _N_ that is a byte offset into the **extended node data** section. The length and
// meaning of the data at that offset is defined by the node type. See the **Extended node data** section for details on
// the format of the extended data for specific node types.
//
// Encoding Arbitrary Nodes
// ------------------------
//
// The same binary format can be used to encode an arbitrary subtree of a SourceFile, not just a whole SourceFile. When
// encoding a non-SourceFile node, the format is identical with the following differences:
//
// - The content hash fields in the header (bytes 4-19) are zero.
// - The parse options field in the header (bytes 20-23) is zero.
// - The root node in the nodes section uses its actual node kind and data encoding (via getNodeData) rather than the
// SourceFile-specific extended data format.
//
// The string data section contains only the strings referenced by nodes in the subtree, rather than the full source
// file text. The EncodeNode function provides this entrypoint.
// SourceFileHash returns the 128-bit content hash for a source file as a hex string.
func SourceFileHash(sourceFile *ast.SourceFile) string {
h := sourceFile.Hash
return fmt.Sprintf("%016x%016x", h.Hi, h.Lo)
}
// encodeParseOptions encodes the per-file ExternalModuleIndicatorOptions as a uint32 bitmask.
func encodeParseOptions(opts ast.ExternalModuleIndicatorOptions) uint32 {
var bits uint32
if opts.JSX {
bits |= 1
}
if opts.Force {
bits |= 2
}
return bits
}
// NodeIndexTable maps between AST nodes and their encoder indices for O(1) node handle resolution.
type NodeIndexTable struct {
Nodes []*ast.Node // index → node (for resolution)
sortedOnce sync.Once
sortedIdx []uint32 // indices into Nodes, sorted by node ID; built lazily
}
var nodeIndexTableKey = ast.NewSourceFileDataKey[*NodeIndexTable]()
// GetIndex returns the encoder index for the given node.
// On the first call the sortedIdx array is built (O(n log n) sort on a flat []uint32),
// then subsequent calls use binary search (O(log n)). This turns out to be much faster than
// building a map[*ast.Node]uint32 and not significantly slower for lookups.
func (t *NodeIndexTable) GetIndex(node *ast.Node) uint32 {
t.sortedOnce.Do(func() {
idx := make([]uint32, 0, len(t.Nodes))
for i, n := range t.Nodes {
if n != nil {
idx = append(idx, uint32(i))
}
}
nodes := t.Nodes
slices.SortFunc(idx, func(a, b uint32) int {
return cmp.Compare(ast.GetNodeId(nodes[a]), ast.GetNodeId(nodes[b]))
})
t.sortedIdx = idx
})
target := ast.GetNodeId(node)
i, found := core.BinarySearchUniqueFunc(t.sortedIdx, func(_ int, el uint32) int {
return cmp.Compare(ast.GetNodeId(t.Nodes[el]), target)
})
if found {
return t.sortedIdx[i]
}
return 0
}
// BuildNodeIndexTable walks the AST in the same order as encodeTree and builds
// a NodeIndexTable without performing the full binary encoding. This is used to
// eagerly create index tables for files that need node handles before getSourceFile
// is called. The indices produced are guaranteed to match those from EncodeSourceFile.
func BuildNodeIndexTable(sourceFile *ast.SourceFile) *NodeIndexTable {
var nodeCount uint32
nodeTable := make([]*ast.Node, 1, sourceFile.NodeCount+1) // index 0 = nil sentinel
visitor := &ast.NodeVisitor{
Hooks: ast.NodeVisitorHooks{
VisitNodes: func(nodeList *ast.NodeList, visitor *ast.NodeVisitor) *ast.NodeList {
if nodeList == nil {
return nodeList
}
nodeCount++
nodeTable = append(nodeTable, nil) // NodeLists are not *ast.Node
visitor.VisitSlice(nodeList.Nodes)
return nodeList
},
VisitModifiers: func(modifiers *ast.ModifierList, visitor *ast.NodeVisitor) *ast.ModifierList {
if modifiers != nil && len(modifiers.Nodes) > 0 {
visitor.Hooks.VisitNodes(&modifiers.NodeList, visitor)
}
return modifiers
},
},
}
visitor.Visit = func(node *ast.Node) *ast.Node {
nodeCount++
nodeTable = append(nodeTable, node)
visitor.VisitEachChild(node)
for _, jsdoc := range node.JSDoc(sourceFile) {
visitor.Visit(jsdoc)
}
return node
}
rootNode := sourceFile.AsNode()
// Index 1 = root node (matches encodeTree)
nodeCount++
nodeTable = append(nodeTable, rootNode)
visitor.VisitEachChild(rootNode)
for _, jsdoc := range rootNode.JSDoc(sourceFile) {
visitor.Visit(jsdoc)
}
return &NodeIndexTable{Nodes: nodeTable}
}
func GetNodeIndexTable(sourceFile *ast.SourceFile) *NodeIndexTable {
return ast.GetOrComputeSourceFileData(sourceFile, nodeIndexTableKey, BuildNodeIndexTable)
}
// EncodeSourceFile encodes an entire source file AST into the binary format.
// Returns the encoded bytes and a NodeIndexTable mapping encoder indices to AST nodes.
func EncodeSourceFile(sourceFile *ast.SourceFile) ([]byte, *NodeIndexTable, error) {
data, nodeTable, err := encodeTree(sourceFile.AsNode(), sourceFile)
if err != nil {
return nil, nil, err
}
nodeTable = ast.GetOrComputeSourceFileData(sourceFile, nodeIndexTableKey, func(*ast.SourceFile) *NodeIndexTable {
return nodeTable
})
return data, nodeTable, nil
}
// EncodeNode encodes an arbitrary AST node and its descendants into the binary format.
// The sourceFile is needed to provide the source text for efficient string encoding.
// When encoding a non-SourceFile node, the header hash and parse options fields will be zero.
// Returns the encoded bytes and a NodeIndexTable mapping encoder indices to AST nodes.
func EncodeNode(node *ast.Node, sourceFile *ast.SourceFile) ([]byte, *NodeIndexTable, error) {
return encodeTree(node, sourceFile)
}
func encodeTree(rootNode *ast.Node, sourceFile *ast.SourceFile) ([]byte, *NodeIndexTable, error) {
var parentIndex, nodeCount, prevIndex uint32
var extendedData []byte
var structuredData []byte
var strs *stringTable
var positionMap *ast.PositionMap
if rootNode.Kind == ast.KindSourceFile {
strs = newStringTable(sourceFile.Text(), sourceFile.TextCount)
positionMap = sourceFile.GetPositionMap()
} else {
strs = newStringTable("", 0)
if sourceFile != nil {
positionMap = sourceFile.GetPositionMap()
}
}
if positionMap == nil {
positionMap = ast.ComputePositionMap("")
}
utf16 := func(pos int) uint32 {
return uint32(positionMap.UTF8ToUTF16(pos))
}
var initialNodeCount int
if sourceFile != nil {
initialNodeCount = sourceFile.NodeCount
}
nodes := make([]byte, 0, (initialNodeCount+1)*NodeSize)
// Build node index table for O(1) handle resolution.
// Index 0 is a nil sentinel; real nodes start at index 1.
nodeTable := make([]*ast.Node, 1, initialNodeCount+1) // index 0 = nil sentinel
// Build a small map of nodes we need to track indices for (imports + moduleAugmentations).
// Values start at 0 and are filled in during the walk.
var nodeIndexMap map[*ast.Node]uint32
var sfExtendedDataOffset int // byte offset in extendedData where SourceFile fields start
if rootNode.Kind == ast.KindSourceFile {
sf := rootNode.AsSourceFile()
total := len(sf.Imports()) + len(sf.ModuleAugmentations)
if sf.ExternalModuleIndicator != nil && sf.ExternalModuleIndicator != rootNode {
total++
}
if total > 0 {
nodeIndexMap = make(map[*ast.Node]uint32, total)
for _, imp := range sf.Imports() {
nodeIndexMap[imp.AsNode()] = 0
}
for _, aug := range sf.ModuleAugmentations {
nodeIndexMap[aug.AsNode()] = 0
}
if sf.ExternalModuleIndicator != nil && sf.ExternalModuleIndicator != rootNode {
nodeIndexMap[sf.ExternalModuleIndicator] = 0
}
}
}
visitor := &ast.NodeVisitor{
Hooks: ast.NodeVisitorHooks{
VisitNodes: func(nodeList *ast.NodeList, visitor *ast.NodeVisitor) *ast.NodeList {
if nodeList == nil {
return nodeList
}
nodeCount++
nodeTable = append(nodeTable, nil) // NodeLists are not *ast.Node
if prevIndex != 0 {
// this is the next sibling of `prevNode`
b0, b1, b2, b3 := uint8(nodeCount), uint8(nodeCount>>8), uint8(nodeCount>>16), uint8(nodeCount>>24)
nodes[prevIndex*NodeSize+NodeOffsetNext+0] = b0
nodes[prevIndex*NodeSize+NodeOffsetNext+1] = b1
nodes[prevIndex*NodeSize+NodeOffsetNext+2] = b2
nodes[prevIndex*NodeSize+NodeOffsetNext+3] = b3
}
nodes = appendUint32s(nodes, SyntaxKindNodeList, utf16(nodeList.Pos()), utf16(nodeList.End()), 0, parentIndex, uint32(len(nodeList.Nodes)), 0)
saveParentIndex := parentIndex
currentIndex := nodeCount
prevIndex = 0
parentIndex = currentIndex
visitor.VisitSlice(nodeList.Nodes)
prevIndex = currentIndex
parentIndex = saveParentIndex
return nodeList
},
VisitModifiers: func(modifiers *ast.ModifierList, visitor *ast.NodeVisitor) *ast.ModifierList {
if modifiers != nil && len(modifiers.Nodes) > 0 {
visitor.Hooks.VisitNodes(&modifiers.NodeList, visitor)
}
return modifiers
},
},
}
visitor.Visit = func(node *ast.Node) *ast.Node {
nodeCount++
nodeTable = append(nodeTable, node)
if prevIndex != 0 {
// this is the next sibling of `prevNode`
b0, b1, b2, b3 := uint8(nodeCount), uint8(nodeCount>>8), uint8(nodeCount>>16), uint8(nodeCount>>24)
nodes[prevIndex*NodeSize+NodeOffsetNext+0] = b0
nodes[prevIndex*NodeSize+NodeOffsetNext+1] = b1
nodes[prevIndex*NodeSize+NodeOffsetNext+2] = b2
nodes[prevIndex*NodeSize+NodeOffsetNext+3] = b3
}
nodes = appendUint32s(nodes, uint32(node.Kind), utf16(node.Pos()), utf16(node.End()), 0, parentIndex, getNodeData(node, strs, positionMap, &extendedData, &structuredData), uint32(node.Flags))
if nodeIndexMap != nil {
if _, ok := nodeIndexMap[node]; ok {
nodeIndexMap[node] = nodeCount
}
}
saveParentIndex := parentIndex
currentIndex := nodeCount
prevIndex = 0
parentIndex = currentIndex
visitor.VisitEachChild(node)
if sourceFile != nil {
for _, jsdoc := range node.JSDoc(sourceFile) {
visitor.Visit(jsdoc)
}
}
prevIndex = currentIndex
parentIndex = saveParentIndex
return node
}
nodes = appendUint32s(nodes, 0, 0, 0, 0, 0, 0, 0)
nodeCount++
parentIndex++
nodeTable = append(nodeTable, rootNode) // index 1 = root node
sfExtendedDataOffset = len(extendedData)
nodes = appendUint32s(nodes, uint32(rootNode.Kind), utf16(rootNode.Pos()), utf16(rootNode.End()), 0, 0, getNodeData(rootNode, strs, positionMap, &extendedData, &structuredData), uint32(rootNode.Flags))
visitor.VisitEachChild(rootNode)
if sourceFile != nil {
for _, jsdoc := range rootNode.JSDoc(sourceFile) {
visitor.Visit(jsdoc)
}
}
var hash xxh3.Uint128
var parseOpts uint32
if rootNode.Kind == ast.KindSourceFile {
hash = sourceFile.Hash
parseOpts = encodeParseOptions(sourceFile.ParseOptions().ExternalModuleIndicatorOptions)
// Encode imports, moduleAugmentations, and ambientModuleNames into structured data,
// and patch the placeholder offsets in the SourceFile extended data.
sf := rootNode.AsSourceFile()
importsOffset := encodeNodeIndexArray(sf.Imports(), nodeIndexMap, &structuredData)
moduleAugmentationsOffset := encodeModuleAugmentations(sf.ModuleAugmentations, nodeIndexMap, &structuredData)
ambientModuleNamesOffset := encodeStringArray(sf.AmbientModuleNames, &structuredData)
// Patch the 3 placeholder uint32s at sfExtendedDataOffset + 32, 36, 40
binary.LittleEndian.PutUint32(extendedData[sfExtendedDataOffset+32:], importsOffset)
binary.LittleEndian.PutUint32(extendedData[sfExtendedDataOffset+36:], moduleAugmentationsOffset)
binary.LittleEndian.PutUint32(extendedData[sfExtendedDataOffset+40:], ambientModuleNamesOffset)
// Patch externalModuleIndicator node index at offset 44
var externalModuleIndicatorIndex uint32
if sf.ExternalModuleIndicator != nil {
if sf.ExternalModuleIndicator == rootNode {
externalModuleIndicatorIndex = 1 // root node index
} else {
externalModuleIndicatorIndex = nodeIndexMap[sf.ExternalModuleIndicator]
}
}
binary.LittleEndian.PutUint32(extendedData[sfExtendedDataOffset+44:], externalModuleIndicatorIndex)
}
metadata := uint32(ProtocolVersion) << 24
offsetStringTableOffsets := HeaderSize
offsetStringTableData := HeaderSize + len(strs.offsets)*4
offsetExtendedData := offsetStringTableData + strs.stringLength()
offsetStructuredData := offsetExtendedData + len(extendedData)
offsetNodes := offsetStructuredData + len(structuredData)
header := []uint32{
metadata,
uint32(hash.Lo), uint32(hash.Lo >> 32),
uint32(hash.Hi), uint32(hash.Hi >> 32),
parseOpts,
uint32(offsetStringTableOffsets),
uint32(offsetStringTableData),
uint32(offsetExtendedData),
uint32(offsetStructuredData),
uint32(offsetNodes),
}
var headerBytes, strsBytes []byte
headerBytes = appendUint32s(nil, header...)
strsBytes = strs.encode()
return slices.Concat(
headerBytes,
strsBytes,
extendedData,
structuredData,
nodes,
), &NodeIndexTable{Nodes: nodeTable}, nil
}
func appendUint32s(buf []byte, values ...uint32) []byte {
for _, value := range values {
buf = binary.LittleEndian.AppendUint32(buf, value)
}
return buf
}
func getNodeData(node *ast.Node, strs *stringTable, positionMap *ast.PositionMap, extendedData *[]byte, structuredData *[]byte) uint32 {
t := getNodeDataType(node)
switch t {
case NodeDataTypeChildren:
return t | getNodeCommonData(node) | uint32(getChildrenPropertyMask(node))
case NodeDataTypeString:
return t | getNodeCommonData(node) | recordNodeStrings(node, strs)
case NodeDataTypeExtendedData:
return t | getNodeCommonData(node) | recordExtendedData(node, strs, positionMap, extendedData, structuredData)
default:
panic("unreachable")
}
}
const noStructuredData = 0xFFFFFFFF
func recordExtendedData_SourceFile(node *ast.Node, strs *stringTable, positionMap *ast.PositionMap, extendedData *[]byte, structuredData *[]byte) {
sf := node.AsSourceFile()
textIndex := strs.add(sf.Text(), sf.Kind, sf.Pos(), sf.End())
fileNameIndex := strs.add(sf.FileName(), 0, 0, 0)
pathIndex := strs.add(string(sf.Path()), 0, 0, 0)
referencedFilesOffset := encodeFileReferences(sf.ReferencedFiles, positionMap, structuredData)
typeRefDirectivesOffset := encodeFileReferences(sf.TypeReferenceDirectives, positionMap, structuredData)
libRefDirectivesOffset := encodeFileReferences(sf.LibReferenceDirectives, positionMap, structuredData)
// imports, moduleAugmentations, ambientModuleNames offsets are placeholders;
// they will be patched after the tree walk when node indices are known.
*extendedData = appendUint32s(*extendedData, textIndex, fileNameIndex, pathIndex, uint32(sf.LanguageVariant), uint32(sf.ScriptKind), referencedFilesOffset, typeRefDirectivesOffset, libRefDirectivesOffset, noStructuredData, noStructuredData, noStructuredData, 0)
}
func recordExtendedData_TemplateHead(node *ast.Node, strs *stringTable, positionMap *ast.PositionMap, extendedData *[]byte, structuredData *[]byte) {
n := node.AsTemplateHead()
textIndex := strs.add(n.Text, node.Kind, node.Pos(), node.End())
rawTextIndex := strs.add(n.RawText, node.Kind, node.Pos(), node.End())
*extendedData = appendUint32s(*extendedData, textIndex, rawTextIndex, uint32(n.TemplateFlags))
}
func recordExtendedData_TemplateMiddle(node *ast.Node, strs *stringTable, positionMap *ast.PositionMap, extendedData *[]byte, structuredData *[]byte) {
n := node.AsTemplateMiddle()
textIndex := strs.add(n.Text, node.Kind, node.Pos(), node.End())
rawTextIndex := strs.add(n.RawText, node.Kind, node.Pos(), node.End())
*extendedData = appendUint32s(*extendedData, textIndex, rawTextIndex, uint32(n.TemplateFlags))
}
func recordExtendedData_TemplateTail(node *ast.Node, strs *stringTable, positionMap *ast.PositionMap, extendedData *[]byte, structuredData *[]byte) {
n := node.AsTemplateTail()
textIndex := strs.add(n.Text, node.Kind, node.Pos(), node.End())
rawTextIndex := strs.add(n.RawText, node.Kind, node.Pos(), node.End())
*extendedData = appendUint32s(*extendedData, textIndex, rawTextIndex, uint32(n.TemplateFlags))
}
func boolToByte(b bool) byte {
if b {
return 1
}
return 0
}
// hasModifiers returns true if the modifier list is non-nil and has at least one modifier.
func hasModifiers(modifiers *ast.ModifierList) bool {
return modifiers != nil && len(modifiers.Nodes) > 0
}
// encodeFileReferences encodes a slice of FileReferences as a msgpack array of tuples
// into the structured data buffer. Returns the byte offset into the buffer, or
// noStructuredData (0xFFFFFFFF) if the slice is empty.
func encodeFileReferences(refs []*ast.FileReference, positionMap *ast.PositionMap, buf *[]byte) uint32 {
if len(refs) == 0 {
return noStructuredData
}
offset := uint32(len(*buf))
*buf = msgpackWriteArrayHeader(*buf, len(refs))
for _, ref := range refs {
// Each entry is a 5-element tuple: [pos, end, fileName, resolutionMode, preserve]
*buf = msgpackWriteArrayHeader(*buf, 5)
*buf = msgpackWriteUint(*buf, uint32(positionMap.UTF8ToUTF16(ref.Pos())))
*buf = msgpackWriteUint(*buf, uint32(positionMap.UTF8ToUTF16(ref.End())))
*buf = msgpackWriteString(*buf, ref.FileName)
*buf = msgpackWriteUint(*buf, uint32(ref.ResolutionMode))
*buf = msgpackWriteBool(*buf, ref.Preserve)
}
return offset
}
// encodeNodeIndexArray encodes a slice of LiteralLikeNodes as a msgpack array of
// uint node indices. Returns the byte offset into the buffer, or noStructuredData
// if the slice is empty.
func encodeNodeIndexArray(nodes []*ast.LiteralLikeNode, indexMap map[*ast.Node]uint32, buf *[]byte) uint32 {
if len(nodes) == 0 {
return noStructuredData
}
offset := uint32(len(*buf))
*buf = msgpackWriteArrayHeader(*buf, len(nodes))
for _, node := range nodes {
*buf = msgpackWriteUint(*buf, indexMap[node.AsNode()])
}
return offset
}
// encodeModuleAugmentations encodes a slice of ModuleName nodes as a msgpack array
// of uint node indices. Returns the byte offset into the buffer, or noStructuredData
// if the slice is empty.
func encodeModuleAugmentations(nodes []*ast.ModuleName, indexMap map[*ast.Node]uint32, buf *[]byte) uint32 {
if len(nodes) == 0 {
return noStructuredData
}
offset := uint32(len(*buf))
*buf = msgpackWriteArrayHeader(*buf, len(nodes))
for _, node := range nodes {
*buf = msgpackWriteUint(*buf, indexMap[node.AsNode()])
}
return offset
}
// encodeStringArray encodes a slice of strings as a msgpack array of strings.
// Returns the byte offset into the buffer, or noStructuredData if the slice is empty.
func encodeStringArray(strs []string, buf *[]byte) uint32 {
if len(strs) == 0 {
return noStructuredData
}
offset := uint32(len(*buf))
*buf = msgpackWriteArrayHeader(*buf, len(strs))
for _, s := range strs {
*buf = msgpackWriteString(*buf, s)
}
return offset
}
// Minimal msgpack writers for the structured data section.
func msgpackWriteArrayHeader(buf []byte, length int) []byte {
if length <= 0x0f {
return append(buf, byte(0x90|length))
}
if length <= 0xffff {
return append(buf, 0xdc, byte(length>>8), byte(length))
}
return append(buf, 0xdd, byte(length>>24), byte(length>>16), byte(length>>8), byte(length))
}
func msgpackWriteUint(buf []byte, value uint32) []byte {
if value <= 0x7f {
return append(buf, byte(value))
}
if value <= 0xff {
return append(buf, 0xcc, byte(value))
}
if value <= 0xffff {
return append(buf, 0xcd, byte(value>>8), byte(value))
}
return append(buf, 0xce, byte(value>>24), byte(value>>16), byte(value>>8), byte(value))
}
func msgpackWriteString(buf []byte, s string) []byte {
n := len(s)
if n <= 0x1f {
buf = append(buf, byte(0xa0|n))
} else if n <= 0xff {
buf = append(buf, 0xd9, byte(n))
} else if n <= 0xffff {
buf = append(buf, 0xda, byte(n>>8), byte(n))
} else {
buf = append(buf, 0xdb, byte(n>>24), byte(n>>16), byte(n>>8), byte(n))
}
return append(buf, s...)
}
func msgpackWriteBool(buf []byte, value bool) []byte {
if value {
return append(buf, 0xc3)
}
return append(buf, 0xc2)
}
// Hand-written commonData encoding functions for nodes whose non-bool data
// members cannot be automatically encoded by the generator. Each function
// packs relevant fields into the 6-bit commonData area (bits 24-29) of the
// 32-bit node data word.
func getNodeCommonData_SyntheticExpression(_ *ast.Node) uint32 {
// SyntheticExpression is an internal compiler node that is never part of a parsed AST.
// It should never be encoded.
panic("SyntheticExpression should never be encoded")
}
// Hand-written extended data encoding functions for literal nodes that were
// previously string-type but whose TokenFlags/TemplateFlags cannot fit in 6 bits.
func recordExtendedData_StringLiteral(node *ast.Node, strs *stringTable, _ *ast.PositionMap, extendedData *[]byte, _ *[]byte) {
n := node.AsStringLiteral()
textIndex := strs.add(n.Text, node.Kind, node.Pos(), node.End())
*extendedData = appendUint32s(*extendedData, textIndex, uint32(n.TokenFlags))
}
func recordExtendedData_NumericLiteral(node *ast.Node, strs *stringTable, _ *ast.PositionMap, extendedData *[]byte, _ *[]byte) {
n := node.AsNumericLiteral()
textIndex := strs.add(n.Text, node.Kind, node.Pos(), node.End())
*extendedData = appendUint32s(*extendedData, textIndex, uint32(n.TokenFlags))
}
func recordExtendedData_BigIntLiteral(node *ast.Node, strs *stringTable, _ *ast.PositionMap, extendedData *[]byte, _ *[]byte) {
n := node.AsBigIntLiteral()
textIndex := strs.add(n.Text, node.Kind, node.Pos(), node.End())
*extendedData = appendUint32s(*extendedData, textIndex, uint32(n.TokenFlags))
}
func recordExtendedData_RegularExpressionLiteral(node *ast.Node, strs *stringTable, _ *ast.PositionMap, extendedData *[]byte, _ *[]byte) {
n := node.AsRegularExpressionLiteral()
textIndex := strs.add(n.Text, node.Kind, node.Pos(), node.End())
*extendedData = appendUint32s(*extendedData, textIndex, uint32(n.TokenFlags))
}
func recordExtendedData_NoSubstitutionTemplateLiteral(node *ast.Node, strs *stringTable, _ *ast.PositionMap, extendedData *[]byte, _ *[]byte) {
n := node.AsNoSubstitutionTemplateLiteral()
textIndex := strs.add(n.Text, node.Kind, node.Pos(), node.End())
*extendedData = appendUint32s(*extendedData, textIndex, uint32(n.TemplateFlags))
}

View File

@@ -0,0 +1,707 @@
// Code generated by _scripts/generate-encoder.ts. DO NOT EDIT.
package encoder
import (
"fmt"
"github.com/microsoft/typescript-go/internal/ast"
)
func getNodeDataType(node *ast.Node) uint32 {
switch node.Kind {
case ast.KindIdentifier,
ast.KindPrivateIdentifier,
ast.KindJsxText,
ast.KindJSDocText,
ast.KindJSDocLink,
ast.KindJSDocLinkPlain,
ast.KindJSDocLinkCode:
return NodeDataTypeString
case ast.KindStringLiteral,
ast.KindNumericLiteral,
ast.KindBigIntLiteral,
ast.KindRegularExpressionLiteral,
ast.KindNoSubstitutionTemplateLiteral,
ast.KindTemplateHead,
ast.KindTemplateMiddle,
ast.KindTemplateTail,
ast.KindSourceFile:
return NodeDataTypeExtendedData
default:
return NodeDataTypeChildren
}
}
func getChildrenPropertyMask(node *ast.Node) uint8 {
switch node.Kind {
case ast.KindQualifiedName:
n := node.AsQualifiedName()
return (boolToByte(n.Left != nil) << 0) | (boolToByte(n.Right != nil) << 1)
case ast.KindComputedPropertyName:
n := node.AsComputedPropertyName()
return (boolToByte(n.Expression != nil) << 0)
case ast.KindDecorator:
n := node.AsDecorator()
return (boolToByte(n.Expression != nil) << 0)
case ast.KindIfStatement:
n := node.AsIfStatement()
return (boolToByte(n.Expression != nil) << 0) | (boolToByte(n.ThenStatement != nil) << 1) | (boolToByte(n.ElseStatement != nil) << 2)
case ast.KindDoStatement:
n := node.AsDoStatement()
return (boolToByte(n.Statement != nil) << 0) | (boolToByte(n.Expression != nil) << 1)
case ast.KindWhileStatement:
n := node.AsWhileStatement()
return (boolToByte(n.Expression != nil) << 0) | (boolToByte(n.Statement != nil) << 1)
case ast.KindForStatement:
n := node.AsForStatement()
return (boolToByte(n.Initializer != nil) << 0) | (boolToByte(n.Condition != nil) << 1) | (boolToByte(n.Incrementor != nil) << 2) | (boolToByte(n.Statement != nil) << 3)
case ast.KindForInStatement, ast.KindForOfStatement:
n := node.AsForInOrOfStatement()
return (boolToByte(n.AwaitModifier != nil) << 0) | (boolToByte(n.Initializer != nil) << 1) | (boolToByte(n.Expression != nil) << 2) | (boolToByte(n.Statement != nil) << 3)
case ast.KindBreakStatement:
n := node.AsBreakStatement()
return (boolToByte(n.Label != nil) << 0)
case ast.KindContinueStatement:
n := node.AsContinueStatement()
return (boolToByte(n.Label != nil) << 0)
case ast.KindReturnStatement:
n := node.AsReturnStatement()
return (boolToByte(n.Expression != nil) << 0)
case ast.KindWithStatement:
n := node.AsWithStatement()
return (boolToByte(n.Expression != nil) << 0) | (boolToByte(n.Statement != nil) << 1)
case ast.KindSwitchStatement:
n := node.AsSwitchStatement()
return (boolToByte(n.Expression != nil) << 0) | (boolToByte(n.CaseBlock != nil) << 1)
case ast.KindCaseBlock:
n := node.AsCaseBlock()
return (boolToByte(n.Clauses != nil) << 0)
case ast.KindCaseClause, ast.KindDefaultClause:
n := node.AsCaseOrDefaultClause()
return (boolToByte(n.Expression != nil) << 0) | (boolToByte(n.Statements != nil) << 1)
case ast.KindThrowStatement:
n := node.AsThrowStatement()
return (boolToByte(n.Expression != nil) << 0)
case ast.KindTryStatement:
n := node.AsTryStatement()
return (boolToByte(n.TryBlock != nil) << 0) | (boolToByte(n.CatchClause != nil) << 1) | (boolToByte(n.FinallyBlock != nil) << 2)
case ast.KindCatchClause:
n := node.AsCatchClause()
return (boolToByte(n.VariableDeclaration != nil) << 0) | (boolToByte(n.Block != nil) << 1)
case ast.KindLabeledStatement:
n := node.AsLabeledStatement()
return (boolToByte(n.Label != nil) << 0) | (boolToByte(n.Statement != nil) << 1)
case ast.KindExpressionStatement:
n := node.AsExpressionStatement()
return (boolToByte(n.Expression != nil) << 0)
case ast.KindBlock:
n := node.AsBlock()
return (boolToByte(n.Statements != nil) << 0)
case ast.KindVariableStatement:
n := node.AsVariableStatement()
return (boolToByte(hasModifiers(n.Modifiers())) << 0) | (boolToByte(n.DeclarationList != nil) << 1)
case ast.KindVariableDeclaration:
n := node.AsVariableDeclaration()
return (boolToByte(n.Name() != nil) << 0) | (boolToByte(n.ExclamationToken != nil) << 1) | (boolToByte(n.Type != nil) << 2) | (boolToByte(n.Initializer != nil) << 3)
case ast.KindVariableDeclarationList:
n := node.AsVariableDeclarationList()
return (boolToByte(n.Declarations != nil) << 0)
case ast.KindObjectBindingPattern, ast.KindArrayBindingPattern:
n := node.AsBindingPattern()
return (boolToByte(n.Elements != nil) << 0)
case ast.KindParameter:
n := node.AsParameterDeclaration()
return (boolToByte(hasModifiers(n.Modifiers())) << 0) | (boolToByte(n.DotDotDotToken != nil) << 1) | (boolToByte(n.Name() != nil) << 2) | (boolToByte(n.QuestionToken != nil) << 3) | (boolToByte(n.Type != nil) << 4) | (boolToByte(n.Initializer != nil) << 5)
case ast.KindBindingElement:
n := node.AsBindingElement()
return (boolToByte(n.DotDotDotToken != nil) << 0) | (boolToByte(n.PropertyName != nil) << 1) | (boolToByte(n.Name() != nil) << 2) | (boolToByte(n.Initializer != nil) << 3)
case ast.KindMissingDeclaration:
n := node.AsMissingDeclaration()
return (boolToByte(hasModifiers(n.Modifiers())) << 0)
case ast.KindFunctionDeclaration:
n := node.AsFunctionDeclaration()
return (boolToByte(hasModifiers(n.Modifiers())) << 0) | (boolToByte(n.AsteriskToken != nil) << 1) | (boolToByte(n.Name() != nil) << 2) | (boolToByte(n.TypeParameters != nil) << 3) | (boolToByte(n.Parameters != nil) << 4) | (boolToByte(n.Type != nil) << 5) | (boolToByte(n.Body != nil) << 6)
case ast.KindClassDeclaration:
n := node.AsClassDeclaration()
return (boolToByte(hasModifiers(n.Modifiers())) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.TypeParameters != nil) << 2) | (boolToByte(n.HeritageClauses != nil) << 3) | (boolToByte(n.Members != nil) << 4)
case ast.KindClassExpression:
n := node.AsClassExpression()
return (boolToByte(hasModifiers(n.Modifiers())) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.TypeParameters != nil) << 2) | (boolToByte(n.HeritageClauses != nil) << 3) | (boolToByte(n.Members != nil) << 4)
case ast.KindHeritageClause:
n := node.AsHeritageClause()
return (boolToByte(n.Types != nil) << 0)
case ast.KindInterfaceDeclaration:
n := node.AsInterfaceDeclaration()
return (boolToByte(hasModifiers(n.Modifiers())) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.TypeParameters != nil) << 2) | (boolToByte(n.HeritageClauses != nil) << 3) | (boolToByte(n.Members != nil) << 4)
case ast.KindTypeAliasDeclaration, ast.KindJSTypeAliasDeclaration:
n := node.AsTypeAliasDeclaration()
return (boolToByte(hasModifiers(n.Modifiers())) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.TypeParameters != nil) << 2) | (boolToByte(n.Type != nil) << 3)
case ast.KindEnumMember:
n := node.AsEnumMember()
return (boolToByte(n.Name() != nil) << 0) | (boolToByte(n.Initializer != nil) << 1)
case ast.KindEnumDeclaration:
n := node.AsEnumDeclaration()
return (boolToByte(hasModifiers(n.Modifiers())) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.Members != nil) << 2)
case ast.KindModuleBlock:
n := node.AsModuleBlock()
return (boolToByte(n.Statements != nil) << 0)
case ast.KindImportDeclaration, ast.KindJSImportDeclaration:
n := node.AsImportDeclaration()
return (boolToByte(hasModifiers(n.Modifiers())) << 0) | (boolToByte(n.ImportClause != nil) << 1) | (boolToByte(n.ModuleSpecifier != nil) << 2) | (boolToByte(n.Attributes != nil) << 3)
case ast.KindExternalModuleReference:
n := node.AsExternalModuleReference()
return (boolToByte(n.Expression != nil) << 0)
case ast.KindNamespaceImport:
n := node.AsNamespaceImport()
return (boolToByte(n.Name() != nil) << 0)
case ast.KindNamedImports:
n := node.AsNamedImports()
return (boolToByte(n.Elements != nil) << 0)
case ast.KindExportAssignment:
n := node.AsExportAssignment()
return (boolToByte(hasModifiers(n.Modifiers())) << 0) | (boolToByte(n.Type != nil) << 1) | (boolToByte(n.Expression != nil) << 2)
case ast.KindNamespaceExportDeclaration:
n := node.AsNamespaceExportDeclaration()
return (boolToByte(hasModifiers(n.Modifiers())) << 0) | (boolToByte(n.Name() != nil) << 1)
case ast.KindNamespaceExport:
n := node.AsNamespaceExport()
return (boolToByte(n.Name() != nil) << 0)
case ast.KindNamedExports:
n := node.AsNamedExports()
return (boolToByte(n.Elements != nil) << 0)
case ast.KindExportSpecifier:
n := node.AsExportSpecifier()
return (boolToByte(n.PropertyName != nil) << 0) | (boolToByte(n.Name() != nil) << 1)
case ast.KindCallSignature:
n := node.AsCallSignatureDeclaration()
return (boolToByte(n.TypeParameters != nil) << 0) | (boolToByte(n.Parameters != nil) << 1) | (boolToByte(n.Type != nil) << 2)
case ast.KindConstructSignature:
n := node.AsConstructSignatureDeclaration()
return (boolToByte(n.TypeParameters != nil) << 0) | (boolToByte(n.Parameters != nil) << 1) | (boolToByte(n.Type != nil) << 2)
case ast.KindConstructor:
n := node.AsConstructorDeclaration()
return (boolToByte(hasModifiers(n.Modifiers())) << 0) | (boolToByte(n.TypeParameters != nil) << 1) | (boolToByte(n.Parameters != nil) << 2) | (boolToByte(n.Type != nil) << 3) | (boolToByte(n.Body != nil) << 4)
case ast.KindGetAccessor:
n := node.AsGetAccessorDeclaration()
return (boolToByte(hasModifiers(n.Modifiers())) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.TypeParameters != nil) << 2) | (boolToByte(n.Parameters != nil) << 3) | (boolToByte(n.Type != nil) << 4) | (boolToByte(n.Body != nil) << 5)
case ast.KindSetAccessor:
n := node.AsSetAccessorDeclaration()
return (boolToByte(hasModifiers(n.Modifiers())) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.TypeParameters != nil) << 2) | (boolToByte(n.Parameters != nil) << 3) | (boolToByte(n.Type != nil) << 4) | (boolToByte(n.Body != nil) << 5)
case ast.KindIndexSignature:
n := node.AsIndexSignatureDeclaration()
return (boolToByte(hasModifiers(n.Modifiers())) << 0) | (boolToByte(n.Parameters != nil) << 1) | (boolToByte(n.Type != nil) << 2)
case ast.KindMethodSignature:
n := node.AsMethodSignatureDeclaration()
return (boolToByte(hasModifiers(n.Modifiers())) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.PostfixToken != nil) << 2) | (boolToByte(n.TypeParameters != nil) << 3) | (boolToByte(n.Parameters != nil) << 4) | (boolToByte(n.Type != nil) << 5)
case ast.KindMethodDeclaration:
n := node.AsMethodDeclaration()
return (boolToByte(hasModifiers(n.Modifiers())) << 0) | (boolToByte(n.AsteriskToken != nil) << 1) | (boolToByte(n.Name() != nil) << 2) | (boolToByte(n.PostfixToken != nil) << 3) | (boolToByte(n.TypeParameters != nil) << 4) | (boolToByte(n.Parameters != nil) << 5) | (boolToByte(n.Type != nil) << 6) | (boolToByte(n.Body != nil) << 7)
case ast.KindPropertySignature:
n := node.AsPropertySignatureDeclaration()
return (boolToByte(hasModifiers(n.Modifiers())) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.PostfixToken != nil) << 2) | (boolToByte(n.Type != nil) << 3) | (boolToByte(n.Initializer != nil) << 4)
case ast.KindPropertyDeclaration:
n := node.AsPropertyDeclaration()
return (boolToByte(hasModifiers(n.Modifiers())) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.PostfixToken != nil) << 2) | (boolToByte(n.Type != nil) << 3) | (boolToByte(n.Initializer != nil) << 4)
case ast.KindClassStaticBlockDeclaration:
n := node.AsClassStaticBlockDeclaration()
return (boolToByte(hasModifiers(n.Modifiers())) << 0) | (boolToByte(n.Body != nil) << 1)
case ast.KindBinaryExpression:
n := node.AsBinaryExpression()
return (boolToByte(hasModifiers(n.Modifiers())) << 0) | (boolToByte(n.Left != nil) << 1) | (boolToByte(n.Type != nil) << 2) | (boolToByte(n.OperatorToken != nil) << 3) | (boolToByte(n.Right != nil) << 4)
case ast.KindPrefixUnaryExpression:
n := node.AsPrefixUnaryExpression()
return (boolToByte(n.Operand != nil) << 0)
case ast.KindPostfixUnaryExpression:
n := node.AsPostfixUnaryExpression()
return (boolToByte(n.Operand != nil) << 0)
case ast.KindYieldExpression:
n := node.AsYieldExpression()
return (boolToByte(n.AsteriskToken != nil) << 0) | (boolToByte(n.Expression != nil) << 1)
case ast.KindArrowFunction:
n := node.AsArrowFunction()
return (boolToByte(hasModifiers(n.Modifiers())) << 0) | (boolToByte(n.TypeParameters != nil) << 1) | (boolToByte(n.Parameters != nil) << 2) | (boolToByte(n.Type != nil) << 3) | (boolToByte(n.EqualsGreaterThanToken != nil) << 4) | (boolToByte(n.Body != nil) << 5)
case ast.KindFunctionExpression:
n := node.AsFunctionExpression()
return (boolToByte(hasModifiers(n.Modifiers())) << 0) | (boolToByte(n.AsteriskToken != nil) << 1) | (boolToByte(n.Name() != nil) << 2) | (boolToByte(n.TypeParameters != nil) << 3) | (boolToByte(n.Parameters != nil) << 4) | (boolToByte(n.Type != nil) << 5) | (boolToByte(n.Body != nil) << 6)
case ast.KindAsExpression:
n := node.AsAsExpression()
return (boolToByte(n.Expression != nil) << 0) | (boolToByte(n.Type != nil) << 1)
case ast.KindSatisfiesExpression:
n := node.AsSatisfiesExpression()
return (boolToByte(n.Expression != nil) << 0) | (boolToByte(n.Type != nil) << 1)
case ast.KindConditionalExpression:
n := node.AsConditionalExpression()
return (boolToByte(n.Condition != nil) << 0) | (boolToByte(n.QuestionToken != nil) << 1) | (boolToByte(n.WhenTrue != nil) << 2) | (boolToByte(n.ColonToken != nil) << 3) | (boolToByte(n.WhenFalse != nil) << 4)
case ast.KindPropertyAccessExpression:
n := node.AsPropertyAccessExpression()
return (boolToByte(n.Expression != nil) << 0) | (boolToByte(n.QuestionDotToken != nil) << 1) | (boolToByte(n.Name() != nil) << 2)
case ast.KindElementAccessExpression:
n := node.AsElementAccessExpression()
return (boolToByte(n.Expression != nil) << 0) | (boolToByte(n.QuestionDotToken != nil) << 1) | (boolToByte(n.ArgumentExpression != nil) << 2)
case ast.KindCallExpression:
n := node.AsCallExpression()
return (boolToByte(n.Expression != nil) << 0) | (boolToByte(n.QuestionDotToken != nil) << 1) | (boolToByte(n.TypeArguments != nil) << 2) | (boolToByte(n.Arguments != nil) << 3)
case ast.KindNewExpression:
n := node.AsNewExpression()
return (boolToByte(n.Expression != nil) << 0) | (boolToByte(n.TypeArguments != nil) << 1) | (boolToByte(n.Arguments != nil) << 2)
case ast.KindMetaProperty:
n := node.AsMetaProperty()
return (boolToByte(n.Name() != nil) << 0)
case ast.KindNonNullExpression:
n := node.AsNonNullExpression()
return (boolToByte(n.Expression != nil) << 0)
case ast.KindSpreadElement:
n := node.AsSpreadElement()
return (boolToByte(n.Expression != nil) << 0)
case ast.KindTemplateExpression:
n := node.AsTemplateExpression()
return (boolToByte(n.Head != nil) << 0) | (boolToByte(n.TemplateSpans != nil) << 1)
case ast.KindTemplateSpan:
n := node.AsTemplateSpan()
return (boolToByte(n.Expression != nil) << 0) | (boolToByte(n.Literal != nil) << 1)
case ast.KindTaggedTemplateExpression:
n := node.AsTaggedTemplateExpression()
return (boolToByte(n.Tag != nil) << 0) | (boolToByte(n.QuestionDotToken != nil) << 1) | (boolToByte(n.TypeArguments != nil) << 2) | (boolToByte(n.Template != nil) << 3)
case ast.KindParenthesizedExpression:
n := node.AsParenthesizedExpression()
return (boolToByte(n.Expression != nil) << 0)
case ast.KindArrayLiteralExpression:
n := node.AsArrayLiteralExpression()
return (boolToByte(n.Elements != nil) << 0)
case ast.KindObjectLiteralExpression:
n := node.AsObjectLiteralExpression()
return (boolToByte(n.Properties != nil) << 0)
case ast.KindSpreadAssignment:
n := node.AsSpreadAssignment()
return (boolToByte(n.Expression != nil) << 0)
case ast.KindPropertyAssignment:
n := node.AsPropertyAssignment()
return (boolToByte(hasModifiers(n.Modifiers())) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.PostfixToken != nil) << 2) | (boolToByte(n.Type != nil) << 3) | (boolToByte(n.Initializer != nil) << 4)
case ast.KindShorthandPropertyAssignment:
n := node.AsShorthandPropertyAssignment()
return (boolToByte(hasModifiers(n.Modifiers())) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.PostfixToken != nil) << 2) | (boolToByte(n.Type != nil) << 3) | (boolToByte(n.EqualsToken != nil) << 4) | (boolToByte(n.ObjectAssignmentInitializer != nil) << 5)
case ast.KindDeleteExpression:
n := node.AsDeleteExpression()
return (boolToByte(n.Expression != nil) << 0)
case ast.KindTypeOfExpression:
n := node.AsTypeOfExpression()
return (boolToByte(n.Expression != nil) << 0)
case ast.KindVoidExpression:
n := node.AsVoidExpression()
return (boolToByte(n.Expression != nil) << 0)
case ast.KindAwaitExpression:
n := node.AsAwaitExpression()
return (boolToByte(n.Expression != nil) << 0)
case ast.KindTypeAssertionExpression:
n := node.AsTypeAssertion()
return (boolToByte(n.Type != nil) << 0) | (boolToByte(n.Expression != nil) << 1)
case ast.KindUnionType:
n := node.AsUnionTypeNode()
return (boolToByte(n.Types != nil) << 0)
case ast.KindIntersectionType:
n := node.AsIntersectionTypeNode()
return (boolToByte(n.Types != nil) << 0)
case ast.KindConditionalType:
n := node.AsConditionalTypeNode()
return (boolToByte(n.CheckType != nil) << 0) | (boolToByte(n.ExtendsType != nil) << 1) | (boolToByte(n.TrueType != nil) << 2) | (boolToByte(n.FalseType != nil) << 3)
case ast.KindTypeOperator:
n := node.AsTypeOperatorNode()
return (boolToByte(n.Type != nil) << 0)
case ast.KindInferType:
n := node.AsInferTypeNode()
return (boolToByte(n.TypeParameter != nil) << 0)
case ast.KindArrayType:
n := node.AsArrayTypeNode()
return (boolToByte(n.ElementType != nil) << 0)
case ast.KindIndexedAccessType:
n := node.AsIndexedAccessTypeNode()
return (boolToByte(n.ObjectType != nil) << 0) | (boolToByte(n.IndexType != nil) << 1)
case ast.KindTypeReference:
n := node.AsTypeReferenceNode()
return (boolToByte(n.TypeName != nil) << 0) | (boolToByte(n.TypeArguments != nil) << 1)
case ast.KindExpressionWithTypeArguments:
n := node.AsExpressionWithTypeArguments()
return (boolToByte(n.Expression != nil) << 0) | (boolToByte(n.TypeArguments != nil) << 1)
case ast.KindLiteralType:
n := node.AsLiteralTypeNode()
return (boolToByte(n.Literal != nil) << 0)
case ast.KindTypePredicate:
n := node.AsTypePredicateNode()
return (boolToByte(n.AssertsModifier != nil) << 0) | (boolToByte(n.ParameterName != nil) << 1) | (boolToByte(n.Type != nil) << 2)
case ast.KindImportAttribute:
n := node.AsImportAttribute()
return (boolToByte(n.Name() != nil) << 0) | (boolToByte(n.Value != nil) << 1)
case ast.KindImportAttributes:
n := node.AsImportAttributes()
return (boolToByte(n.Attributes != nil) << 0)
case ast.KindTypeQuery:
n := node.AsTypeQueryNode()
return (boolToByte(n.ExprName != nil) << 0) | (boolToByte(n.TypeArguments != nil) << 1)
case ast.KindMappedType:
n := node.AsMappedTypeNode()
return (boolToByte(n.ReadonlyToken != nil) << 0) | (boolToByte(n.TypeParameter != nil) << 1) | (boolToByte(n.NameType != nil) << 2) | (boolToByte(n.QuestionToken != nil) << 3) | (boolToByte(n.Type != nil) << 4) | (boolToByte(n.Members != nil) << 5)
case ast.KindTypeLiteral:
n := node.AsTypeLiteralNode()
return (boolToByte(n.Members != nil) << 0)
case ast.KindTupleType:
n := node.AsTupleTypeNode()
return (boolToByte(n.Elements != nil) << 0)
case ast.KindNamedTupleMember:
n := node.AsNamedTupleMember()
return (boolToByte(n.DotDotDotToken != nil) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.QuestionToken != nil) << 2) | (boolToByte(n.Type != nil) << 3)
case ast.KindOptionalType:
n := node.AsOptionalTypeNode()
return (boolToByte(n.Type != nil) << 0)
case ast.KindRestType:
n := node.AsRestTypeNode()
return (boolToByte(n.Type != nil) << 0)
case ast.KindParenthesizedType:
n := node.AsParenthesizedTypeNode()
return (boolToByte(n.Type != nil) << 0)
case ast.KindFunctionType:
n := node.AsFunctionTypeNode()
return (boolToByte(n.TypeParameters != nil) << 0) | (boolToByte(n.Parameters != nil) << 1) | (boolToByte(n.Type != nil) << 2)
case ast.KindConstructorType:
n := node.AsConstructorTypeNode()
return (boolToByte(hasModifiers(n.Modifiers())) << 0) | (boolToByte(n.TypeParameters != nil) << 1) | (boolToByte(n.Parameters != nil) << 2) | (boolToByte(n.Type != nil) << 3)
case ast.KindTemplateLiteralType:
n := node.AsTemplateLiteralTypeNode()
return (boolToByte(n.Head != nil) << 0) | (boolToByte(n.TemplateSpans != nil) << 1)
case ast.KindTemplateLiteralTypeSpan:
n := node.AsTemplateLiteralTypeSpan()
return (boolToByte(n.Type != nil) << 0) | (boolToByte(n.Literal != nil) << 1)
case ast.KindSyntheticExpression:
n := node.AsSyntheticExpression()
return (boolToByte(n.TupleNameSource != nil) << 0)
case ast.KindPartiallyEmittedExpression:
n := node.AsPartiallyEmittedExpression()
return (boolToByte(n.Expression != nil) << 0)
case ast.KindJsxElement:
n := node.AsJsxElement()
return (boolToByte(n.OpeningElement != nil) << 0) | (boolToByte(n.Children != nil) << 1) | (boolToByte(n.ClosingElement != nil) << 2)
case ast.KindJsxAttributes:
n := node.AsJsxAttributes()
return (boolToByte(n.Properties != nil) << 0)
case ast.KindJsxNamespacedName:
n := node.AsJsxNamespacedName()
return (boolToByte(n.Namespace != nil) << 0) | (boolToByte(n.Name() != nil) << 1)
case ast.KindJsxOpeningElement:
n := node.AsJsxOpeningElement()
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.TypeArguments != nil) << 1) | (boolToByte(n.Attributes != nil) << 2)
case ast.KindJsxSelfClosingElement:
n := node.AsJsxSelfClosingElement()
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.TypeArguments != nil) << 1) | (boolToByte(n.Attributes != nil) << 2)
case ast.KindJsxFragment:
n := node.AsJsxFragment()
return (boolToByte(n.OpeningFragment != nil) << 0) | (boolToByte(n.Children != nil) << 1) | (boolToByte(n.ClosingFragment != nil) << 2)
case ast.KindJsxAttribute:
n := node.AsJsxAttribute()
return (boolToByte(n.Name() != nil) << 0) | (boolToByte(n.Initializer != nil) << 1)
case ast.KindJsxSpreadAttribute:
n := node.AsJsxSpreadAttribute()
return (boolToByte(n.Expression != nil) << 0)
case ast.KindJsxClosingElement:
n := node.AsJsxClosingElement()
return (boolToByte(n.TagName != nil) << 0)
case ast.KindJsxExpression:
n := node.AsJsxExpression()
return (boolToByte(n.DotDotDotToken != nil) << 0) | (boolToByte(n.Expression != nil) << 1)
case ast.KindSyntaxList:
n := node.AsSyntaxList()
return (boolToByte(len(n.Children) > 0) << 0)
case ast.KindJSDoc:
n := node.AsJSDoc()
return (boolToByte(n.Comment != nil) << 0) | (boolToByte(n.Tags != nil) << 1)
case ast.KindJSDocTypeExpression:
n := node.AsJSDocTypeExpression()
return (boolToByte(n.Type != nil) << 0)
case ast.KindJSDocNonNullableType:
n := node.AsJSDocNonNullableType()
return (boolToByte(n.Type != nil) << 0)
case ast.KindJSDocNullableType:
n := node.AsJSDocNullableType()
return (boolToByte(n.Type != nil) << 0)
case ast.KindJSDocVariadicType:
n := node.AsJSDocVariadicType()
return (boolToByte(n.Type != nil) << 0)
case ast.KindJSDocOptionalType:
n := node.AsJSDocOptionalType()
return (boolToByte(n.Type != nil) << 0)
case ast.KindJSDocTypeTag:
n := node.AsJSDocTypeTag()
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.TypeExpression != nil) << 1) | (boolToByte(n.Comment != nil) << 2)
case ast.KindJSDocUnknownTag:
n := node.AsJSDocUnknownTag()
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.Comment != nil) << 1)
case ast.KindJSDocTemplateTag:
n := node.AsJSDocTemplateTag()
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.Constraint != nil) << 1) | (boolToByte(n.TypeParameters != nil) << 2) | (boolToByte(n.Comment != nil) << 3)
case ast.KindJSDocReturnTag:
n := node.AsJSDocReturnTag()
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.TypeExpression != nil) << 1) | (boolToByte(n.Comment != nil) << 2)
case ast.KindJSDocPublicTag:
n := node.AsJSDocPublicTag()
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.Comment != nil) << 1)
case ast.KindJSDocPrivateTag:
n := node.AsJSDocPrivateTag()
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.Comment != nil) << 1)
case ast.KindJSDocProtectedTag:
n := node.AsJSDocProtectedTag()
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.Comment != nil) << 1)
case ast.KindJSDocReadonlyTag:
n := node.AsJSDocReadonlyTag()
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.Comment != nil) << 1)
case ast.KindJSDocOverrideTag:
n := node.AsJSDocOverrideTag()
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.Comment != nil) << 1)
case ast.KindJSDocDeprecatedTag:
n := node.AsJSDocDeprecatedTag()
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.Comment != nil) << 1)
case ast.KindJSDocSeeTag:
n := node.AsJSDocSeeTag()
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.NameExpression != nil) << 1) | (boolToByte(n.Comment != nil) << 2)
case ast.KindJSDocImplementsTag:
n := node.AsJSDocImplementsTag()
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.ClassName != nil) << 1) | (boolToByte(n.Comment != nil) << 2)
case ast.KindJSDocAugmentsTag:
n := node.AsJSDocAugmentsTag()
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.ClassName != nil) << 1) | (boolToByte(n.Comment != nil) << 2)
case ast.KindJSDocSatisfiesTag:
n := node.AsJSDocSatisfiesTag()
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.TypeExpression != nil) << 1) | (boolToByte(n.Comment != nil) << 2)
case ast.KindJSDocThrowsTag:
n := node.AsJSDocThrowsTag()
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.TypeExpression != nil) << 1) | (boolToByte(n.Comment != nil) << 2)
case ast.KindJSDocThisTag:
n := node.AsJSDocThisTag()
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.TypeExpression != nil) << 1) | (boolToByte(n.Comment != nil) << 2)
case ast.KindJSDocImportTag:
n := node.AsJSDocImportTag()
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.ImportClause != nil) << 1) | (boolToByte(n.ModuleSpecifier != nil) << 2) | (boolToByte(n.Attributes != nil) << 3) | (boolToByte(n.Comment != nil) << 4)
case ast.KindJSDocCallbackTag:
n := node.AsJSDocCallbackTag()
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.TypeExpression != nil) << 1) | (boolToByte(n.Name() != nil) << 2) | (boolToByte(n.Comment != nil) << 3)
case ast.KindJSDocOverloadTag:
n := node.AsJSDocOverloadTag()
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.TypeExpression != nil) << 1) | (boolToByte(n.Comment != nil) << 2)
case ast.KindJSDocTypedefTag:
n := node.AsJSDocTypedefTag()
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.TypeExpression != nil) << 1) | (boolToByte(n.Name() != nil) << 2) | (boolToByte(n.Comment != nil) << 3)
case ast.KindJSDocSignature:
n := node.AsJSDocSignature()
return (boolToByte(n.TypeParameters != nil) << 0) | (boolToByte(n.Parameters != nil) << 1) | (boolToByte(n.Type != nil) << 2)
case ast.KindJSDocNameReference:
n := node.AsJSDocNameReference()
return (boolToByte(n.Name() != nil) << 0)
case ast.KindModuleDeclaration:
n := node.AsModuleDeclaration()
return (boolToByte(hasModifiers(n.Modifiers())) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.Body != nil) << 2)
case ast.KindImportEqualsDeclaration:
n := node.AsImportEqualsDeclaration()
return (boolToByte(hasModifiers(n.Modifiers())) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.ModuleReference != nil) << 2)
case ast.KindExportDeclaration:
n := node.AsExportDeclaration()
return (boolToByte(hasModifiers(n.Modifiers())) << 0) | (boolToByte(n.ExportClause != nil) << 1) | (boolToByte(n.ModuleSpecifier != nil) << 2) | (boolToByte(n.Attributes != nil) << 3)
case ast.KindImportType:
n := node.AsImportTypeNode()
return (boolToByte(n.Argument != nil) << 0) | (boolToByte(n.Attributes != nil) << 1) | (boolToByte(n.Qualifier != nil) << 2) | (boolToByte(n.TypeArguments != nil) << 3)
case ast.KindImportClause:
n := node.AsImportClause()
return (boolToByte(n.Name() != nil) << 0) | (boolToByte(n.NamedBindings != nil) << 1)
case ast.KindImportSpecifier:
n := node.AsImportSpecifier()
return (boolToByte(n.PropertyName != nil) << 0) | (boolToByte(n.Name() != nil) << 1)
case ast.KindJSDocLink:
n := node.AsJSDocLink()
return (boolToByte(n.Name() != nil) << 0)
case ast.KindJSDocLinkPlain:
n := node.AsJSDocLinkPlain()
return (boolToByte(n.Name() != nil) << 0)
case ast.KindJSDocLinkCode:
n := node.AsJSDocLinkCode()
return (boolToByte(n.Name() != nil) << 0)
case ast.KindTypeParameter:
n := node.AsTypeParameterDeclaration()
return (boolToByte(hasModifiers(n.Modifiers())) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.Constraint != nil) << 2) | (boolToByte(n.Expression != nil) << 3) | (boolToByte(n.DefaultType != nil) << 4)
case ast.KindSyntheticReferenceExpression:
n := node.AsSyntheticReferenceExpression()
return (boolToByte(n.Expression != nil) << 0) | (boolToByte(n.ThisArg != nil) << 1)
case ast.KindJSDocTypeLiteral:
n := node.AsJSDocTypeLiteral()
return (boolToByte(len(n.JSDocPropertyTags) > 0) << 0)
case ast.KindJSDocParameterTag, ast.KindJSDocPropertyTag:
n := node.AsJSDocParameterOrPropertyTag()
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.TypeExpression != nil) << 2) | (boolToByte(n.Comment != nil) << 3)
default:
return 0
}
}
func getNodeCommonData(node *ast.Node) uint32 {
switch node.Kind {
case ast.KindBlock:
n := node.AsBlock()
return uint32(boolToByte(n.MultiLine)) << 24
case ast.KindHeritageClause:
n := node.AsHeritageClause()
var tokenIdx uint32
switch n.Token {
case ast.KindImplementsKeyword:
tokenIdx = 1
}
return tokenIdx << 24
case ast.KindExportAssignment:
n := node.AsExportAssignment()
return uint32(boolToByte(n.IsExportEquals)) << 24
case ast.KindExportSpecifier:
n := node.AsExportSpecifier()
return uint32(boolToByte(n.IsTypeOnly)) << 24
case ast.KindPrefixUnaryExpression:
n := node.AsPrefixUnaryExpression()
var operatorIdx uint32
switch n.Operator {
case ast.KindMinusToken:
operatorIdx = 1
case ast.KindTildeToken:
operatorIdx = 2
case ast.KindExclamationToken:
operatorIdx = 3
case ast.KindPlusPlusToken:
operatorIdx = 4
case ast.KindMinusMinusToken:
operatorIdx = 5
}
return operatorIdx << 24
case ast.KindPostfixUnaryExpression:
n := node.AsPostfixUnaryExpression()
var operatorIdx uint32
switch n.Operator {
case ast.KindMinusMinusToken:
operatorIdx = 1
}
return operatorIdx << 24
case ast.KindMetaProperty:
n := node.AsMetaProperty()
var keywordTokenIdx uint32
switch n.KeywordToken {
case ast.KindNewKeyword:
keywordTokenIdx = 1
}
return keywordTokenIdx << 24
case ast.KindArrayLiteralExpression:
n := node.AsArrayLiteralExpression()
return uint32(boolToByte(n.MultiLine)) << 24
case ast.KindObjectLiteralExpression:
n := node.AsObjectLiteralExpression()
return uint32(boolToByte(n.MultiLine)) << 24
case ast.KindTypeOperator:
n := node.AsTypeOperatorNode()
var operatorIdx uint32
switch n.Operator {
case ast.KindReadonlyKeyword:
operatorIdx = 1
case ast.KindUniqueKeyword:
operatorIdx = 2
}
return operatorIdx << 24
case ast.KindImportAttributes:
n := node.AsImportAttributes()
var tokenIdx uint32
switch n.Token {
case ast.KindAssertKeyword:
tokenIdx = 1
}
return uint32(boolToByte(n.MultiLine))<<24 | tokenIdx<<25
case ast.KindSyntheticExpression:
return getNodeCommonData_SyntheticExpression(node)
case ast.KindJsxText:
n := node.AsJsxText()
return uint32(boolToByte(n.ContainsOnlyTriviaWhiteSpaces)) << 24
case ast.KindModuleDeclaration:
n := node.AsModuleDeclaration()
var keywordIdx uint32
switch n.Keyword {
case ast.KindNamespaceKeyword:
keywordIdx = 1
}
return keywordIdx << 24
case ast.KindImportEqualsDeclaration:
n := node.AsImportEqualsDeclaration()
return uint32(boolToByte(n.IsTypeOnly)) << 24
case ast.KindExportDeclaration:
n := node.AsExportDeclaration()
return uint32(boolToByte(n.IsTypeOnly)) << 24
case ast.KindImportType:
n := node.AsImportTypeNode()
return uint32(boolToByte(n.IsTypeOf)) << 24
case ast.KindImportClause:
n := node.AsImportClause()
var phaseModifierIdx uint32
switch n.PhaseModifier {
case ast.KindTypeKeyword:
phaseModifierIdx = 1
case ast.KindDeferKeyword:
phaseModifierIdx = 2
}
return phaseModifierIdx << 24
case ast.KindImportSpecifier:
n := node.AsImportSpecifier()
return uint32(boolToByte(n.IsTypeOnly)) << 24
case ast.KindJSDocTypeLiteral:
n := node.AsJSDocTypeLiteral()
return uint32(boolToByte(n.IsArrayType)) << 24
case ast.KindJSDocParameterTag, ast.KindJSDocPropertyTag:
n := node.AsJSDocParameterOrPropertyTag()
return uint32(boolToByte(n.IsBracketed))<<24 | uint32(boolToByte(n.IsNameFirst))<<25
}
return 0
}
func recordNodeStrings(node *ast.Node, strs *stringTable) uint32 {
switch node.Kind {
case ast.KindIdentifier:
return strs.add(node.AsIdentifier().Text, node.Kind, node.Pos(), node.End())
case ast.KindPrivateIdentifier:
return strs.add(node.AsPrivateIdentifier().Text, node.Kind, node.Pos(), node.End())
case ast.KindJsxText:
return strs.add(node.AsJsxText().Text, node.Kind, node.Pos(), node.End())
case ast.KindJSDocText:
return strs.add(node.AsJSDocText().Text(), node.Kind, node.Pos(), node.End())
case ast.KindJSDocLink:
return strs.add(node.AsJSDocLink().Text(), node.Kind, node.Pos(), node.End())
case ast.KindJSDocLinkPlain:
return strs.add(node.AsJSDocLinkPlain().Text(), node.Kind, node.Pos(), node.End())
case ast.KindJSDocLinkCode:
return strs.add(node.AsJSDocLinkCode().Text(), node.Kind, node.Pos(), node.End())
default:
panic(fmt.Sprintf("Unexpected node kind %v", node.Kind))
}
}
func recordExtendedData(node *ast.Node, strs *stringTable, positionMap *ast.PositionMap, extendedData *[]byte, structuredData *[]byte) uint32 {
offset := uint32(len(*extendedData))
switch node.Kind {
case ast.KindStringLiteral:
recordExtendedData_StringLiteral(node, strs, positionMap, extendedData, structuredData)
case ast.KindNumericLiteral:
recordExtendedData_NumericLiteral(node, strs, positionMap, extendedData, structuredData)
case ast.KindBigIntLiteral:
recordExtendedData_BigIntLiteral(node, strs, positionMap, extendedData, structuredData)
case ast.KindRegularExpressionLiteral:
recordExtendedData_RegularExpressionLiteral(node, strs, positionMap, extendedData, structuredData)
case ast.KindNoSubstitutionTemplateLiteral:
recordExtendedData_NoSubstitutionTemplateLiteral(node, strs, positionMap, extendedData, structuredData)
case ast.KindTemplateHead:
recordExtendedData_TemplateHead(node, strs, positionMap, extendedData, structuredData)
case ast.KindTemplateMiddle:
recordExtendedData_TemplateMiddle(node, strs, positionMap, extendedData, structuredData)
case ast.KindTemplateTail:
recordExtendedData_TemplateTail(node, strs, positionMap, extendedData, structuredData)
case ast.KindSourceFile:
recordExtendedData_SourceFile(node, strs, positionMap, extendedData, structuredData)
default:
panic(fmt.Sprintf("unknown extended data node kind %v", node.Kind))
}
return offset
}

View File

@@ -0,0 +1,161 @@
package encoder_test
import (
"encoding/binary"
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"github.com/microsoft/typescript-go/internal/api/encoder"
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/parser"
"github.com/microsoft/typescript-go/internal/repo"
"github.com/microsoft/typescript-go/internal/testutil/baseline"
"gotest.tools/v3/assert"
)
func TestEncodeSourceFile(t *testing.T) {
t.Parallel()
sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{
FileName: "/test.ts",
Path: "/test.ts",
}, "import { bar } from \"bar\";\nexport function foo<T, U>(a: string, b: string): any {}\nfoo();", core.ScriptKindTS)
t.Run("baseline", func(t *testing.T) {
t.Parallel()
buf, _, err := encoder.EncodeSourceFile(sourceFile)
assert.NilError(t, err)
str := formatEncodedSourceFile(buf)
baseline.Run(t, "encodeSourceFile.txt", str, baseline.Options{
Subfolder: "api",
})
})
}
func TestEncodeSourceFileWithUnicodeEscapes(t *testing.T) {
t.Parallel()
sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{
FileName: "/test.ts",
Path: "/test.ts",
}, `let a = "😃"; let b = "\ud83d\ude03"; let c = "\udc00\ud83d\ude03"; let d = "\ud83d\ud83d\ude03"`, core.ScriptKindTS)
t.Run("baseline", func(t *testing.T) {
t.Parallel()
buf, _, err := encoder.EncodeSourceFile(sourceFile)
assert.NilError(t, err)
str := formatEncodedSourceFile(buf)
baseline.Run(t, "encodeSourceFileWithUnicodeEscapes.txt", str, baseline.Options{
Subfolder: "api",
})
})
}
func TestBuildNodeIndexTableMatchesEncode(t *testing.T) {
t.Parallel()
sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{
FileName: "/test.ts",
Path: "/test.ts",
}, "import { bar } from \"bar\";\nexport function foo<T, U>(a: string, b: string): any {}\nfoo();", core.ScriptKindTS)
_, encodeTable, err := encoder.EncodeSourceFile(sourceFile)
assert.NilError(t, err)
buildTable := encoder.BuildNodeIndexTable(sourceFile)
// Both tables should produce identical Nodes slices
assert.Equal(t, len(buildTable.Nodes), len(encodeTable.Nodes), "Nodes slice length mismatch")
// Every index should map to the same node
for i := range encodeTable.Nodes {
assert.Equal(t, buildTable.Nodes[i], encodeTable.Nodes[i], "node mismatch at index %d", i)
}
// GetIndex on both tables should agree for every non-nil node
for i, node := range encodeTable.Nodes {
if node == nil {
continue
}
encIdx := encodeTable.GetIndex(node)
buildIdx := buildTable.GetIndex(node)
assert.Equal(t, encIdx, uint32(i), "encodeTable.GetIndex mismatch at index %d, node kind=%s", i, node.Kind.String())
assert.Equal(t, buildIdx, encIdx, "buildTable.GetIndex mismatch for node kind=%s", node.Kind.String())
}
}
func BenchmarkEncodeSourceFile(b *testing.B) {
repo.SkipIfNoTypeScriptSubmodule(b)
filePath := filepath.Join(repo.TypeScriptSubmodulePath(), "src/compiler/checker.ts")
fileContent, err := os.ReadFile(filePath)
assert.NilError(b, err)
sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{
FileName: "/checker.ts",
Path: "/checker.ts",
}, string(fileContent), core.ScriptKindTS)
for b.Loop() {
_, _, err := encoder.EncodeSourceFile(sourceFile)
assert.NilError(b, err)
}
}
func BenchmarkBuildNodeIndexTable(b *testing.B) {
repo.SkipIfNoTypeScriptSubmodule(b)
filePath := filepath.Join(repo.TypeScriptSubmodulePath(), "src/compiler/checker.ts")
fileContent, err := os.ReadFile(filePath)
assert.NilError(b, err)
sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{
FileName: "/checker.ts",
Path: "/checker.ts",
}, string(fileContent), core.ScriptKindTS)
for b.Loop() {
encoder.BuildNodeIndexTable(sourceFile)
}
}
func readUint32(buf []byte, offset int) uint32 {
return binary.LittleEndian.Uint32(buf[offset : offset+4])
}
func formatEncodedSourceFile(encoded []byte) string {
var result strings.Builder
var getIndent func(parentIndex uint32) string
offsetNodes := readUint32(encoded, encoder.HeaderOffsetNodes)
offsetStringOffsets := readUint32(encoded, encoder.HeaderOffsetStringOffsets)
offsetStrings := readUint32(encoded, encoder.HeaderOffsetStringData)
getIndent = func(parentIndex uint32) string {
if parentIndex == 0 {
return ""
}
return " " + getIndent(readUint32(encoded, int(offsetNodes)+int(parentIndex)*encoder.NodeSize+encoder.NodeOffsetParent))
}
j := 1
for i := int(offsetNodes) + encoder.NodeSize; i < len(encoded); i += encoder.NodeSize {
kind := readUint32(encoded, i+encoder.NodeOffsetKind)
pos := readUint32(encoded, i+encoder.NodeOffsetPos)
end := readUint32(encoded, i+encoder.NodeOffsetEnd)
parentIndex := readUint32(encoded, i+encoder.NodeOffsetParent)
result.WriteString(getIndent(parentIndex))
if kind == encoder.SyntaxKindNodeList {
result.WriteString("NodeList")
} else {
result.WriteString(ast.Kind(kind).String())
}
data := readUint32(encoded, i+encoder.NodeOffsetData)
dataType := data & encoder.NodeDataTypeMask
if ast.Kind(kind) == ast.KindIdentifier || (dataType == encoder.NodeDataTypeString) {
stringIndex := data & encoder.NodeDataStringIndexMask
strStart := readUint32(encoded, int(offsetStringOffsets+stringIndex*4))
strEnd := readUint32(encoded, int(offsetStringOffsets+stringIndex*4)+4)
str := string(encoded[offsetStrings+strStart : offsetStrings+strEnd])
result.WriteString(fmt.Sprintf(" \"%s\"", str))
}
fmt.Fprintf(&result, " [%d, %d), i=%d, next=%d", pos, end, j, encoded[i+encoder.NodeOffsetNext])
result.WriteString("\n")
j++
}
return result.String()
}

View File

@@ -0,0 +1,68 @@
package encoder
import (
"strings"
"github.com/microsoft/typescript-go/internal/ast"
)
type stringTable struct {
fileText string
otherStrings *strings.Builder
// offsets are pos/end pairs
offsets []uint32
}
func newStringTable(fileText string, stringCount int) *stringTable {
builder := &strings.Builder{}
return &stringTable{
fileText: fileText,
otherStrings: builder,
offsets: make([]uint32, 0, stringCount*2),
}
}
func (t *stringTable) add(text string, kind ast.Kind, pos int, end int) uint32 {
index := uint32(len(t.offsets))
if kind == ast.KindSourceFile {
t.offsets = append(t.offsets, uint32(pos), uint32(end))
return index
}
length := len(text)
if end-pos > 0 && end <= len(t.fileText) {
// pos includes leading trivia, but we can usually infer the actual start of the
// string from the kind and end
endOffset := 0
if kind == ast.KindStringLiteral || kind == ast.KindTemplateTail || kind == ast.KindNoSubstitutionTemplateLiteral {
endOffset = 1
}
end = end - endOffset
start := end - length
fileSlice := t.fileText[start:end]
if fileSlice == text {
t.offsets = append(t.offsets, uint32(start), uint32(end))
return index
}
}
// no exact match, so we need to add it to the string table
offset := len(t.fileText) + t.otherStrings.Len()
t.otherStrings.WriteString(text)
t.offsets = append(t.offsets, uint32(offset), uint32(offset+length))
return index
}
func (t *stringTable) encode() []byte {
result := make([]byte, 0, t.encodedLength())
result = appendUint32s(result, t.offsets...)
result = append(result, t.fileText...)
result = append(result, t.otherStrings.String()...)
return result
}
func (t *stringTable) stringLength() int {
return len(t.fileText) + t.otherStrings.Len()
}
func (t *stringTable) encodedLength() int {
return len(t.offsets)*4 + len(t.fileText) + t.otherStrings.Len()
}

View File

@@ -0,0 +1,14 @@
package encoder_test
import (
"testing"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/testutil/baseline"
)
func TestMain(m *testing.M) {
core.ApplyDebugStackLimit()
defer baseline.Track()()
m.Run()
}