kittenipc/kitcom/internal/tsgo/execute/incremental/snapshottobuildinfo.go
2025-10-15 10:12:44 +03:00

364 lines
13 KiB
Go

package incremental
import (
"fmt"
"maps"
"reflect"
"slices"
"strings"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/collections"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/compiler"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tsoptions"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath"
)
func snapshotToBuildInfo(snapshot *snapshot, program *compiler.Program, buildInfoFileName string) *BuildInfo {
buildInfo := &BuildInfo{
Version: core.Version(),
}
to := &toBuildInfo{
snapshot: snapshot,
program: program,
buildInfo: buildInfo,
buildInfoDirectory: tspath.GetDirectoryPath(buildInfoFileName),
comparePathsOptions: tspath.ComparePathsOptions{
CurrentDirectory: program.GetCurrentDirectory(),
UseCaseSensitiveFileNames: program.UseCaseSensitiveFileNames(),
},
fileNameToFileId: make(map[string]BuildInfoFileId),
fileNamesToFileIdListId: make(map[string]BuildInfoFileIdListId),
roots: make(map[*ast.SourceFile]tspath.Path),
}
if snapshot.options.IsIncremental() {
to.collectRootFiles()
to.setFileInfoAndEmitSignatures()
to.setRootOfIncrementalProgram()
to.setCompilerOptions()
to.setReferencedMap()
to.setChangeFileSet()
to.setSemanticDiagnostics()
to.setEmitDiagnostics()
to.setAffectedFilesPendingEmit()
if snapshot.latestChangedDtsFile != "" {
buildInfo.LatestChangedDtsFile = to.relativeToBuildInfo(snapshot.latestChangedDtsFile)
}
} else {
to.setRootOfNonIncrementalProgram()
}
buildInfo.Errors = snapshot.hasErrors.IsTrue()
buildInfo.SemanticErrors = snapshot.hasSemanticErrors
buildInfo.CheckPending = snapshot.checkPending
return buildInfo
}
type toBuildInfo struct {
snapshot *snapshot
program *compiler.Program
buildInfo *BuildInfo
buildInfoDirectory string
comparePathsOptions tspath.ComparePathsOptions
fileNameToFileId map[string]BuildInfoFileId
fileNamesToFileIdListId map[string]BuildInfoFileIdListId
roots map[*ast.SourceFile]tspath.Path
}
func (t *toBuildInfo) relativeToBuildInfo(path string) string {
return tspath.EnsurePathIsNonModuleName(tspath.GetRelativePathFromDirectory(t.buildInfoDirectory, path, t.comparePathsOptions))
}
func (t *toBuildInfo) toFileId(path tspath.Path) BuildInfoFileId {
fileId := t.fileNameToFileId[string(path)]
if fileId == 0 {
if libFile := t.program.GetDefaultLibFile(path); libFile != nil && !libFile.Replaced {
t.buildInfo.FileNames = append(t.buildInfo.FileNames, libFile.Name)
} else {
t.buildInfo.FileNames = append(t.buildInfo.FileNames, t.relativeToBuildInfo(string(path)))
}
fileId = BuildInfoFileId(len(t.buildInfo.FileNames))
t.fileNameToFileId[string(path)] = fileId
}
return fileId
}
func (t *toBuildInfo) toFileIdListId(set *collections.Set[tspath.Path]) BuildInfoFileIdListId {
fileIds := core.Map(slices.Collect(maps.Keys(set.Keys())), t.toFileId)
slices.Sort(fileIds)
key := strings.Join(core.Map(fileIds, func(id BuildInfoFileId) string {
return fmt.Sprintf("%d", id)
}), ",")
fileIdListId := t.fileNamesToFileIdListId[key]
if fileIdListId == 0 {
t.buildInfo.FileIdsList = append(t.buildInfo.FileIdsList, fileIds)
fileIdListId = BuildInfoFileIdListId(len(t.buildInfo.FileIdsList))
t.fileNamesToFileIdListId[key] = fileIdListId
}
return fileIdListId
}
func (t *toBuildInfo) toRelativeToBuildInfoCompilerOptionValue(option *tsoptions.CommandLineOption, v any) any {
if option.Kind == "list" {
if option.Elements().IsFilePath {
if arr, ok := v.([]string); ok {
return core.Map(arr, t.relativeToBuildInfo)
}
}
} else if option.IsFilePath {
if str, ok := v.(string); ok && str != "" {
return t.relativeToBuildInfo(v.(string))
}
}
return v
}
func (t *toBuildInfo) toBuildInfoDiagnosticsFromFileNameDiagnostics(diagnostics []*buildInfoDiagnosticWithFileName) []*BuildInfoDiagnostic {
return core.Map(diagnostics, func(d *buildInfoDiagnosticWithFileName) *BuildInfoDiagnostic {
var file BuildInfoFileId
if d.file != "" {
file = t.toFileId(d.file)
}
return &BuildInfoDiagnostic{
File: file,
NoFile: d.noFile,
Pos: d.pos,
End: d.end,
Code: d.code,
Category: d.category,
Message: d.message,
MessageChain: t.toBuildInfoDiagnosticsFromFileNameDiagnostics(d.messageChain),
RelatedInformation: t.toBuildInfoDiagnosticsFromFileNameDiagnostics(d.relatedInformation),
ReportsUnnecessary: d.reportsUnnecessary,
ReportsDeprecated: d.reportsDeprecated,
SkippedOnNoEmit: d.skippedOnNoEmit,
}
})
}
func (t *toBuildInfo) toBuildInfoDiagnosticsFromDiagnostics(filePath tspath.Path, diagnostics []*ast.Diagnostic) []*BuildInfoDiagnostic {
return core.Map(diagnostics, func(d *ast.Diagnostic) *BuildInfoDiagnostic {
var file BuildInfoFileId
noFile := false
if d.File() == nil {
noFile = true
} else if d.File().Path() != filePath {
file = t.toFileId(d.File().Path())
}
return &BuildInfoDiagnostic{
File: file,
NoFile: noFile,
Pos: d.Loc().Pos(),
End: d.Loc().End(),
Code: d.Code(),
Category: d.Category(),
Message: d.Message(),
MessageChain: t.toBuildInfoDiagnosticsFromDiagnostics(filePath, d.MessageChain()),
RelatedInformation: t.toBuildInfoDiagnosticsFromDiagnostics(filePath, d.RelatedInformation()),
ReportsUnnecessary: d.ReportsUnnecessary(),
ReportsDeprecated: d.ReportsDeprecated(),
SkippedOnNoEmit: d.SkippedOnNoEmit(),
}
})
}
func (t *toBuildInfo) toBuildInfoDiagnosticsOfFile(filePath tspath.Path, diags *diagnosticsOrBuildInfoDiagnosticsWithFileName) *BuildInfoDiagnosticsOfFile {
if len(diags.diagnostics) > 0 {
return &BuildInfoDiagnosticsOfFile{
FileId: t.toFileId(filePath),
Diagnostics: t.toBuildInfoDiagnosticsFromDiagnostics(filePath, diags.diagnostics),
}
}
if len(diags.buildInfoDiagnostics) > 0 {
return &BuildInfoDiagnosticsOfFile{
FileId: t.toFileId(filePath),
Diagnostics: t.toBuildInfoDiagnosticsFromFileNameDiagnostics(diags.buildInfoDiagnostics),
}
}
return nil
}
func (t *toBuildInfo) collectRootFiles() {
for _, fileName := range t.program.CommandLine().FileNames() {
var file *ast.SourceFile
if redirect := t.program.GetParseFileRedirect(fileName); redirect != "" {
file = t.program.GetSourceFile(redirect)
} else {
file = t.program.GetSourceFile(fileName)
}
if file != nil {
t.roots[file] = tspath.ToPath(fileName, t.comparePathsOptions.CurrentDirectory, t.comparePathsOptions.UseCaseSensitiveFileNames)
}
}
}
func (t *toBuildInfo) setFileInfoAndEmitSignatures() {
t.buildInfo.FileInfos = core.MapNonNil(t.program.GetSourceFiles(), func(file *ast.SourceFile) *BuildInfoFileInfo {
info, _ := t.snapshot.fileInfos.Load(file.Path())
fileId := t.toFileId(file.Path())
// tryAddRoot(key, fileId);
if t.buildInfo.FileNames[fileId-1] != t.relativeToBuildInfo(string(file.Path())) {
if libFile := t.program.GetDefaultLibFile(file.Path()); libFile == nil || libFile.Replaced || t.buildInfo.FileNames[fileId-1] != libFile.Name {
panic(fmt.Sprintf("File name at index %d does not match expected relative path or libName: %s != %s", fileId-1, t.buildInfo.FileNames[fileId-1], t.relativeToBuildInfo(string(file.Path()))))
}
}
if int(fileId) != len(t.buildInfo.FileNames) {
// Duplicate - for now ignore
return nil
}
if t.snapshot.options.Composite.IsTrue() {
if !ast.IsJsonSourceFile(file) && t.program.SourceFileMayBeEmitted(file, false) {
if emitSignature, loaded := t.snapshot.emitSignatures.Load(file.Path()); !loaded {
t.buildInfo.EmitSignatures = append(t.buildInfo.EmitSignatures, &BuildInfoEmitSignature{
FileId: fileId,
})
} else if emitSignature.signature != info.signature {
incrementalEmitSignature := &BuildInfoEmitSignature{
FileId: fileId,
}
if emitSignature.signature != "" {
incrementalEmitSignature.Signature = emitSignature.signature
} else if emitSignature.signatureWithDifferentOptions[0] == info.signature {
incrementalEmitSignature.DiffersOnlyInDtsMap = true
} else {
incrementalEmitSignature.Signature = emitSignature.signatureWithDifferentOptions[0]
incrementalEmitSignature.DiffersInOptions = true
}
t.buildInfo.EmitSignatures = append(t.buildInfo.EmitSignatures, incrementalEmitSignature)
}
}
}
return newBuildInfoFileInfo(info)
})
if t.buildInfo.FileInfos == nil {
t.buildInfo.FileInfos = []*BuildInfoFileInfo{}
}
}
func (t *toBuildInfo) setRootOfIncrementalProgram() {
keys := slices.Collect(maps.Keys(t.roots))
slices.SortFunc(keys, func(a, b *ast.SourceFile) int {
return int(t.toFileId(a.Path())) - int(t.toFileId(b.Path()))
})
for _, file := range keys {
root := t.toFileId(t.roots[file])
resolved := t.toFileId(file.Path())
if t.buildInfo.Root == nil {
// First fileId as is
t.buildInfo.Root = append(t.buildInfo.Root, &BuildInfoRoot{Start: resolved})
} else {
last := t.buildInfo.Root[len(t.buildInfo.Root)-1]
if last.End == resolved-1 {
// If its [..., last = [start, end = fileId - 1]], update last to [start, fileId]
last.End = resolved
} else if last.End == 0 && last.Start == resolved-1 {
// If its [..., last = start = fileId - 1 ], update last to [start, fileId]
last.End = resolved
} else {
t.buildInfo.Root = append(t.buildInfo.Root, &BuildInfoRoot{Start: resolved})
}
}
if root != resolved {
t.buildInfo.ResolvedRoot = append(t.buildInfo.ResolvedRoot, &BuildInfoResolvedRoot{
Resolved: resolved,
Root: root,
})
}
}
}
func (t *toBuildInfo) setCompilerOptions() {
tsoptions.ForEachCompilerOptionValue(
t.snapshot.options,
func(option *tsoptions.CommandLineOption) bool {
return option.AffectsBuildInfo
},
func(option *tsoptions.CommandLineOption, value reflect.Value, i int) bool {
if value.IsZero() {
return false
}
// Make it relative to buildInfo directory if file path
if t.buildInfo.Options == nil {
t.buildInfo.Options = &collections.OrderedMap[string, any]{}
}
t.buildInfo.Options.Set(option.Name, t.toRelativeToBuildInfoCompilerOptionValue(option, value.Interface()))
return false
},
)
}
func (t *toBuildInfo) setReferencedMap() {
keys := t.snapshot.referencedMap.getPathsWithReferences()
slices.Sort(keys)
t.buildInfo.ReferencedMap = core.Map(keys, func(filePath tspath.Path) *BuildInfoReferenceMapEntry {
references, _ := t.snapshot.referencedMap.getReferences(filePath)
return &BuildInfoReferenceMapEntry{
FileId: t.toFileId(filePath),
FileIdListId: t.toFileIdListId(references),
}
})
}
func (t *toBuildInfo) setChangeFileSet() {
files := slices.Collect(t.snapshot.changedFilesSet.Keys())
slices.Sort(files)
t.buildInfo.ChangeFileSet = core.Map(files, t.toFileId)
}
func (t *toBuildInfo) setSemanticDiagnostics() {
for _, file := range t.program.GetSourceFiles() {
value, ok := t.snapshot.semanticDiagnosticsPerFile.Load(file.Path())
if !ok {
if !t.snapshot.changedFilesSet.Has(file.Path()) {
t.buildInfo.SemanticDiagnosticsPerFile = append(t.buildInfo.SemanticDiagnosticsPerFile, &BuildInfoSemanticDiagnostic{
FileId: t.toFileId(file.Path()),
})
}
} else {
diagnostics := t.toBuildInfoDiagnosticsOfFile(file.Path(), value)
if diagnostics != nil {
t.buildInfo.SemanticDiagnosticsPerFile = append(t.buildInfo.SemanticDiagnosticsPerFile, &BuildInfoSemanticDiagnostic{
Diagnostics: diagnostics,
})
}
}
}
}
func (t *toBuildInfo) setEmitDiagnostics() {
files := slices.Collect(t.snapshot.emitDiagnosticsPerFile.Keys())
slices.Sort(files)
t.buildInfo.EmitDiagnosticsPerFile = core.Map(files, func(filePath tspath.Path) *BuildInfoDiagnosticsOfFile {
value, _ := t.snapshot.emitDiagnosticsPerFile.Load(filePath)
return t.toBuildInfoDiagnosticsOfFile(filePath, value)
})
}
func (t *toBuildInfo) setAffectedFilesPendingEmit() {
files := slices.Collect(t.snapshot.affectedFilesPendingEmit.Keys())
slices.Sort(files)
fullEmitKind := GetFileEmitKind(t.snapshot.options)
for _, filePath := range files {
file := t.program.GetSourceFileByPath(filePath)
if file == nil || !t.program.SourceFileMayBeEmitted(file, false) {
continue
}
pendingEmit, _ := t.snapshot.affectedFilesPendingEmit.Load(filePath)
t.buildInfo.AffectedFilesPendingEmit = append(t.buildInfo.AffectedFilesPendingEmit, &BuildInfoFilePendingEmit{
FileId: t.toFileId(filePath),
EmitKind: core.IfElse(pendingEmit == fullEmitKind, 0, pendingEmit),
})
}
}
func (t *toBuildInfo) setRootOfNonIncrementalProgram() {
t.buildInfo.Root = core.Map(t.program.CommandLine().FileNames(), func(fileName string) *BuildInfoRoot {
return &BuildInfoRoot{
NonIncremental: t.relativeToBuildInfo(string(tspath.ToPath(fileName, t.comparePathsOptions.CurrentDirectory, t.comparePathsOptions.UseCaseSensitiveFileNames))),
}
})
}