162 lines
5.6 KiB
Go
162 lines
5.6 KiB
Go
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()
|
|
}
|