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,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)
}
})
}