192 lines
4.8 KiB
Go
192 lines
4.8 KiB
Go
package compiler_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/microsoft/typescript-go/internal/bundled"
|
|
"github.com/microsoft/typescript-go/internal/compiler"
|
|
"github.com/microsoft/typescript-go/internal/core"
|
|
"github.com/microsoft/typescript-go/internal/tsoptions"
|
|
"github.com/microsoft/typescript-go/internal/vfs/vfstest"
|
|
)
|
|
|
|
// generateLongLineTS generates TypeScript source code that produces a single very long line.
|
|
// This simulates generated code (e.g., from code generators) that has no line breaks,
|
|
// which triggers O(n²) behavior in source map generation due to
|
|
// GetECMALineAndUTF16CharacterOfPosition scanning from line start for each position.
|
|
func generateLongLineTS(numProperties int) string {
|
|
// Build a large object literal all on one line, with no line breaks.
|
|
var b strings.Builder
|
|
b.WriteString("export const data: Record<string, number> = {")
|
|
for i := range numProperties {
|
|
if i > 0 {
|
|
b.WriteString(", ")
|
|
}
|
|
fmt.Fprintf(&b, "prop_%d: %d", i, i)
|
|
}
|
|
b.WriteString("};")
|
|
return b.String()
|
|
}
|
|
|
|
func BenchmarkEmitLongLines(b *testing.B) {
|
|
if !bundled.Embedded {
|
|
b.Skip("bundled files are not embedded")
|
|
}
|
|
|
|
for _, numProps := range []int{1000, 5000, 10000} {
|
|
b.Run(fmt.Sprintf("props_%d", numProps), func(b *testing.B) {
|
|
source := generateLongLineTS(numProps)
|
|
|
|
fs := vfstest.FromMap(map[string]string{
|
|
"/dev/src/index.ts": source,
|
|
}, true /*useCaseSensitiveFileNames*/)
|
|
fs = bundled.WrapFS(fs)
|
|
|
|
opts := core.CompilerOptions{
|
|
Target: core.ScriptTargetES2015,
|
|
SourceMap: core.TSTrue,
|
|
OutDir: "/dev/out",
|
|
}
|
|
|
|
host := compiler.NewCompilerHost("/dev/src", fs, bundled.LibPath(), nil, nil)
|
|
|
|
p := compiler.NewProgram(compiler.ProgramOptions{
|
|
Config: &tsoptions.ParsedCommandLine{
|
|
ParsedConfig: &core.ParsedOptions{
|
|
FileNames: []string{"/dev/src/index.ts"},
|
|
CompilerOptions: &opts,
|
|
},
|
|
},
|
|
Host: host,
|
|
})
|
|
|
|
// Discard written files — we only care about emit performance.
|
|
nopWriteFile := func(fileName string, text string, data *compiler.WriteFileData) error {
|
|
return nil
|
|
}
|
|
|
|
b.ResetTimer()
|
|
b.ReportAllocs()
|
|
|
|
for b.Loop() {
|
|
p.Emit(context.Background(), compiler.EmitOptions{
|
|
WriteFile: nopWriteFile,
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func BenchmarkEmitManyFiles(b *testing.B) {
|
|
if !bundled.Embedded {
|
|
b.Skip("bundled files are not embedded")
|
|
}
|
|
|
|
// Simulate many files with moderately long single-line content.
|
|
numFiles := 200
|
|
numPropsPerFile := 500
|
|
|
|
files := make(map[string]string, numFiles)
|
|
fileNames := make([]string, 0, numFiles)
|
|
for i := range numFiles {
|
|
name := fmt.Sprintf("/dev/src/file_%d.ts", i)
|
|
files[name] = generateLongLineTS(numPropsPerFile)
|
|
fileNames = append(fileNames, name)
|
|
}
|
|
|
|
fs := vfstest.FromMap(files, true)
|
|
fs = bundled.WrapFS(fs)
|
|
|
|
opts := core.CompilerOptions{
|
|
Target: core.ScriptTargetES2015,
|
|
SourceMap: core.TSTrue,
|
|
OutDir: "/dev/out",
|
|
}
|
|
|
|
host := compiler.NewCompilerHost("/dev/src", fs, bundled.LibPath(), nil, nil)
|
|
|
|
p := compiler.NewProgram(compiler.ProgramOptions{
|
|
Config: &tsoptions.ParsedCommandLine{
|
|
ParsedConfig: &core.ParsedOptions{
|
|
FileNames: fileNames,
|
|
CompilerOptions: &opts,
|
|
},
|
|
},
|
|
Host: host,
|
|
})
|
|
|
|
nopWriteFile := func(fileName string, text string, data *compiler.WriteFileData) error {
|
|
return nil
|
|
}
|
|
|
|
b.ResetTimer()
|
|
b.ReportAllocs()
|
|
|
|
for b.Loop() {
|
|
p.Emit(context.Background(), compiler.EmitOptions{
|
|
WriteFile: nopWriteFile,
|
|
})
|
|
}
|
|
}
|
|
|
|
// BenchmarkEmitLongLinesWithLineBreaks is a control benchmark that emits the same amount
|
|
// of code but WITH line breaks, showing that the issue is specific to long lines.
|
|
func BenchmarkEmitLongLinesWithLineBreaks(b *testing.B) {
|
|
if !bundled.Embedded {
|
|
b.Skip("bundled files are not embedded")
|
|
}
|
|
|
|
numProperties := 10000
|
|
|
|
// Same content but with newlines between each property.
|
|
var sb strings.Builder
|
|
sb.WriteString("export const data: Record<string, number> = {\n")
|
|
for i := range numProperties {
|
|
if i > 0 {
|
|
sb.WriteString(",\n")
|
|
}
|
|
fmt.Fprintf(&sb, " prop_%d: %d", i, i)
|
|
}
|
|
sb.WriteString("\n};\n")
|
|
source := sb.String()
|
|
|
|
fs := vfstest.FromMap(map[string]string{
|
|
"/dev/src/index.ts": source,
|
|
}, true)
|
|
fs = bundled.WrapFS(fs)
|
|
|
|
opts := core.CompilerOptions{
|
|
Target: core.ScriptTargetES2015,
|
|
SourceMap: core.TSTrue,
|
|
OutDir: "/dev/out",
|
|
}
|
|
|
|
host := compiler.NewCompilerHost("/dev/src", fs, bundled.LibPath(), nil, nil)
|
|
|
|
p := compiler.NewProgram(compiler.ProgramOptions{
|
|
Config: &tsoptions.ParsedCommandLine{
|
|
ParsedConfig: &core.ParsedOptions{
|
|
FileNames: []string{"/dev/src/index.ts"},
|
|
CompilerOptions: &opts,
|
|
},
|
|
},
|
|
Host: host,
|
|
})
|
|
|
|
nopWriteFile := func(fileName string, text string, data *compiler.WriteFileData) error {
|
|
return nil
|
|
}
|
|
|
|
b.ResetTimer()
|
|
b.ReportAllocs()
|
|
|
|
for b.Loop() {
|
|
p.Emit(context.Background(), compiler.EmitOptions{
|
|
WriteFile: nopWriteFile,
|
|
})
|
|
}
|
|
}
|