Files
kjol/tools/tsgo/internal/bundled/generate.go
2026-07-09 16:50:43 -04:00

218 lines
5.5 KiB
Go

//go:build ignore
package main
import (
"bytes"
"encoding/json"
"fmt"
"go/format"
"log"
"os"
"path/filepath"
"regexp"
"slices"
"strings"
"github.com/microsoft/typescript-go/internal/repo"
)
var (
libInputDir = filepath.Join(repo.TypeScriptSubmodulePath(), "src", "lib")
copyrightNotice = filepath.Join(repo.TypeScriptSubmodulePath(), "scripts", "CopyrightNotice.txt")
)
func main() {
log.SetFlags(log.LstdFlags | log.Lshortfile)
libs := readLibs()
generateLibs(libs)
generateLibList(libs)
generateEmbedded(libs)
}
type lib struct {
target string // target relative to libs dir
sources []string // sources relative to src/lib dir
}
func generateLibs(libs []lib) {
const outputDir = "libs"
copyright := readCopyright()
if err := os.RemoveAll(outputDir); err != nil {
log.Fatalf("failed to remove libs directory: %v", err)
}
if err := os.MkdirAll(outputDir, 0o755); err != nil {
log.Fatalf("failed to create libs directory: %v", err)
}
for _, lib := range libs {
var output bytes.Buffer
output.Write(copyright)
for _, source := range lib.sources {
sourcePath := filepath.Join(libInputDir, source)
b, err := os.ReadFile(sourcePath)
if err != nil {
log.Fatalf("failed to read %s: %v", sourcePath, err)
}
output.WriteByte('\n')
output.Write(removeCRLF(b))
}
outputPath := filepath.Join(outputDir, lib.target)
if err := os.WriteFile(outputPath, output.Bytes(), 0o644); err != nil {
log.Fatalf("failed to write %s: %v", outputPath, err)
}
}
}
func generateLibList(libs []lib) {
var code bytes.Buffer
code.WriteString("// Code generated by generate.go; DO NOT EDIT.\n\n")
code.WriteString("package bundled\n\n")
code.WriteString("// LibNames is the list of all bundled lib files, sorted by name.\n")
code.WriteString("// For the list of libs sorted by load order, use [tsoptions.Libs].\n")
code.WriteString("var LibNames = []string{\n")
for _, lib := range libs {
code.WriteString("\t\"" + lib.target + "\",\n")
}
code.WriteString("}\n")
writeCode("libs_generated.go", code.Bytes())
}
func generateEmbedded(libs []lib) {
libVarNames := make([]string, len(libs))
for i, lib := range libs {
libVarNames[i] = "libs_" + strings.ReplaceAll(lib.target, ".", "_")
}
var code bytes.Buffer
code.WriteString("//go:build !noembed\n\n")
code.WriteString("// Code generated by generate.go; DO NOT EDIT.\n\n")
code.WriteString("package bundled\n\n")
code.WriteString("import (\n")
code.WriteString("\"io/fs\"\n\n")
code.WriteString("_ \"embed\"\n")
code.WriteString(")\n\n")
code.WriteString("var (\n")
for i, lib := range libs {
varName := libVarNames[i]
code.WriteString("//go:embed libs/" + lib.target + "\n")
code.WriteString("" + varName + " string\n")
}
code.WriteString(")\n\n")
code.WriteString("var embeddedContents = map[string]string{\n")
for i, lib := range libs {
varName := libVarNames[i]
code.WriteString("\t\"libs/" + lib.target + "\": " + varName + ",\n")
}
code.WriteString("}\n\n")
code.WriteString("var libsEntries = []fs.DirEntry{\n")
for i, lib := range libs {
varName := libVarNames[i]
fmt.Fprintf(&code, "\t&fileInfo{name: %q, size: int64(len(%s))},\n", lib.target, varName)
}
code.WriteString("}\n")
writeCode("embed_generated.go", code.Bytes())
}
var (
// Match escaped characters, double-quoted strings, single-line comments, and multi-line comments.
reJSONComments = regexp.MustCompile(`\\.|"(?:\\.|[^"])*"|//.*|/\*[\s\S]*?\*/`)
// Match double-quoted strings (to skip) or trailing commas before ] or }.
reTrailingComma = regexp.MustCompile(`"(?:\\.|[^"])*"|,\s*([}\]])`)
)
// stripJSONC replaces comments and trailing commas with spaces in JSONC content,
// producing valid JSON. The input slice is mutated in place.
func stripJSONC(b []byte) {
for _, loc := range reJSONComments.FindAllIndex(b, -1) {
if b[loc[0]] == '/' {
for i := loc[0]; i < loc[1]; i++ {
if b[i] != '\n' {
b[i] = ' '
}
}
}
}
for _, loc := range reTrailingComma.FindAllSubmatchIndex(b, -1) {
// loc[2]:loc[3] is the capture group; -1 means this matched a string, not a comma.
if loc[2] < 0 {
continue
}
// Blank the comma (at loc[0]), keep whitespace and closing bracket.
b[loc[0]] = ' '
}
}
func readLibs() []lib {
libsFile := filepath.Join(libInputDir, "libs.json")
b, err := os.ReadFile(libsFile)
if err != nil {
log.Fatalf("failed to open libs.json: %v", err)
}
stripJSONC(b)
var meta struct {
Libs []string `json:"libs"`
Paths map[string]string `json:"paths"`
}
if err := json.Unmarshal(b, &meta); err != nil {
log.Fatalf("failed to parse libs.json: %v", err)
}
var libs []lib
for _, libName := range meta.Libs {
sources := []string{libName + ".d.ts"}
var target string
if path, ok := meta.Paths[libName]; ok {
target = path
} else {
target = "lib." + libName + ".d.ts"
}
libs = append(libs, lib{target: target, sources: sources})
}
slices.SortFunc(libs, func(a lib, b lib) int {
return strings.Compare(a.target, b.target)
})
return libs
}
func readCopyright() []byte {
b, err := os.ReadFile(copyrightNotice)
if err != nil {
log.Fatalf("failed to read copyright notice: %v", err)
}
return removeCRLF(b)
}
func removeCRLF(b []byte) []byte {
return bytes.ReplaceAll(b, []byte("\r\n"), []byte("\n"))
}
func writeCode(filename string, code []byte) {
formatted, err := format.Source(code)
if err != nil {
log.Fatalf("failed to format source: %v", err)
}
if err := os.WriteFile(filename, formatted, 0o644); err != nil {
log.Fatalf("failed to write %s: %v", filename, err)
}
}