package tsctests import ( "fmt" "strings" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/collections" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/core" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/diagnostics" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/execute/incremental" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/jsonutil" "github.com/go-json-experiment/json" ) type readableBuildInfo struct { buildInfo *incremental.BuildInfo Version string `json:"version,omitzero"` // Common between incremental and tsc -b buildinfo for non incremental programs Errors bool `json:"errors,omitzero"` CheckPending bool `json:"checkPending,omitzero"` Root []*readableBuildInfoRoot `json:"root,omitzero"` // IncrementalProgram info FileNames []string `json:"fileNames,omitzero"` FileInfos []*readableBuildInfoFileInfo `json:"fileInfos,omitzero"` FileIdsList [][]string `json:"fileIdsList,omitzero"` Options *collections.OrderedMap[string, any] `json:"options,omitzero"` ReferencedMap *collections.OrderedMap[string, []string] `json:"referencedMap,omitzero"` SemanticDiagnosticsPerFile []*readableBuildInfoSemanticDiagnostic `json:"semanticDiagnosticsPerFile,omitzero"` EmitDiagnosticsPerFile []*readableBuildInfoDiagnosticsOfFile `json:"emitDiagnosticsPerFile,omitzero"` ChangeFileSet []string `json:"changeFileSet,omitzero"` // List of changed files in the program, not the whole set of files AffectedFilesPendingEmit []*readableBuildInfoFilePendingEmit `json:"affectedFilesPendingEmit,omitzero"` LatestChangedDtsFile string `json:"latestChangedDtsFile,omitzero"` // Because this is only output file in the program, we dont need fileId to deduplicate name EmitSignatures []*readableBuildInfoEmitSignature `json:"emitSignatures,omitzero"` ResolvedRoot []*readableBuildInfoResolvedRoot `json:"resolvedRoot,omitzero"` Size int `json:"size,omitzero"` // Size of the build info file // NonIncrementalProgram info SemanticErrors bool `json:"semanticErrors,omitzero"` } type readableBuildInfoRoot struct { Files []string `json:"files,omitzero"` Original *incremental.BuildInfoRoot `json:"original,omitzero"` } type readableBuildInfoFileInfo struct { FileName string `json:"fileName,omitzero"` Version string `json:"version,omitzero"` Signature string `json:"signature,omitzero"` AffectsGlobalScope bool `json:"affectsGlobalScope,omitzero"` ImpliedNodeFormat string `json:"impliedNodeFormat,omitzero"` Original *incremental.BuildInfoFileInfo `json:"original,omitzero"` // Original file path, if available } type readableBuildInfoDiagnostic struct { // incrementalBuildInfoFileId if it is for a File thats other than its stored for File string `json:"file,omitzero"` NoFile bool `json:"noFile,omitzero"` Pos int `json:"pos,omitzero"` End int `json:"end,omitzero"` Code int32 `json:"code,omitzero"` Category diagnostics.Category `json:"category,omitzero"` Message string `json:"message,omitzero"` MessageChain []*readableBuildInfoDiagnostic `json:"messageChain,omitzero"` RelatedInformation []*readableBuildInfoDiagnostic `json:"relatedInformation,omitzero"` ReportsUnnecessary bool `json:"reportsUnnecessary,omitzero"` ReportsDeprecated bool `json:"reportsDeprecated,omitzero"` SkippedOnNoEmit bool `json:"skippedOnNoEmit,omitzero"` } type readableBuildInfoDiagnosticsOfFile struct { file string diagnostics []*readableBuildInfoDiagnostic } func (r *readableBuildInfoDiagnosticsOfFile) MarshalJSON() ([]byte, error) { fileIdAndDiagnostics := make([]any, 0, 2) fileIdAndDiagnostics = append(fileIdAndDiagnostics, r.file) fileIdAndDiagnostics = append(fileIdAndDiagnostics, r.diagnostics) return json.Marshal(fileIdAndDiagnostics) } func (r *readableBuildInfoDiagnosticsOfFile) UnmarshalJSON(data []byte) error { var fileIdAndDiagnostics []any if err := json.Unmarshal(data, &fileIdAndDiagnostics); err != nil { return fmt.Errorf("invalid readableBuildInfoDiagnosticsOfFile: %s", data) } if len(fileIdAndDiagnostics) != 2 { return fmt.Errorf("invalid readableBuildInfoDiagnosticsOfFile: expected 2 elements, got %d", len(fileIdAndDiagnostics)) } file, ok := fileIdAndDiagnostics[0].(string) if !ok { return fmt.Errorf("invalid fileId in readableBuildInfoDiagnosticsOfFile: expected string, got %T", fileIdAndDiagnostics[0]) } if diagnostics, ok := fileIdAndDiagnostics[1].([]*readableBuildInfoDiagnostic); !ok { return fmt.Errorf("invalid diagnostics in readableBuildInfoDiagnosticsOfFile: expected []*readableBuildInfoDiagnostic, got %T", fileIdAndDiagnostics[1]) } else { *r = readableBuildInfoDiagnosticsOfFile{ file: file, diagnostics: diagnostics, } return nil } } type readableBuildInfoSemanticDiagnostic struct { file string // File is not in changedSet and still doesnt have cached diagnostics diagnostics *readableBuildInfoDiagnosticsOfFile // Diagnostics for file } func (r *readableBuildInfoSemanticDiagnostic) MarshalJSON() ([]byte, error) { if r.file != "" { return json.Marshal(r.file) } return json.Marshal(r.diagnostics) } func (r *readableBuildInfoSemanticDiagnostic) UnmarshalJSON(data []byte) error { var file string if err := json.Unmarshal(data, &file); err != nil { var diagnostics readableBuildInfoDiagnosticsOfFile if err := json.Unmarshal(data, &diagnostics); err != nil { return fmt.Errorf("invalid readableBuildInfoSemanticDiagnostic: %s", data) } *r = readableBuildInfoSemanticDiagnostic{ diagnostics: &diagnostics, } return nil } *r = readableBuildInfoSemanticDiagnostic{ file: file, } return nil } type readableBuildInfoFilePendingEmit struct { file string emitKind string original *incremental.BuildInfoFilePendingEmit } func (b *readableBuildInfoFilePendingEmit) MarshalJSON() ([]byte, error) { return json.Marshal([]any{b.file, b.emitKind, b.original}) } func (b *readableBuildInfoFilePendingEmit) UnmarshalJSON(data []byte) error { var fileIdAndEmitKind []any if err := json.Unmarshal(data, &fileIdAndEmitKind); err != nil { return fmt.Errorf("invalid readableBuildInfoFilePendingEmit: %s", data) } if len(fileIdAndEmitKind) != 3 { return fmt.Errorf("invalid readableBuildInfoFilePendingEmit: expected 3 elements, got %d", len(fileIdAndEmitKind)) } file, ok := fileIdAndEmitKind[0].(string) if !ok { return fmt.Errorf("invalid fileId in readableBuildInfoFilePendingEmit: expected string, got %T", fileIdAndEmitKind[0]) } var emitKind string emitKind, ok = fileIdAndEmitKind[1].(string) if !ok { return fmt.Errorf("invalid emitKind in readableBuildInfoFilePendingEmit: expected string, got %T", fileIdAndEmitKind[1]) } var original *incremental.BuildInfoFilePendingEmit original, ok = fileIdAndEmitKind[2].(*incremental.BuildInfoFilePendingEmit) if !ok { return fmt.Errorf("invalid original in readableBuildInfoFilePendingEmit: expected *incremental.BuildInfoFilePendingEmit, got %T", fileIdAndEmitKind[2]) } *b = readableBuildInfoFilePendingEmit{ file: file, emitKind: emitKind, original: original, } return nil } type readableBuildInfoEmitSignature struct { File string `json:"file,omitzero"` Signature string `json:"signature,omitzero"` DiffersOnlyInDtsMap bool `json:"differsOnlyInDtsMap,omitzero"` DiffersInOptions bool `json:"differsInOptions,omitzero"` Original *incremental.BuildInfoEmitSignature `json:"original,omitzero"` } type readableBuildInfoResolvedRoot struct { Resolved string Root string } func (b *readableBuildInfoResolvedRoot) MarshalJSON() ([]byte, error) { return json.Marshal([2]string{b.Resolved, b.Root}) } func (b *readableBuildInfoResolvedRoot) UnmarshalJSON(data []byte) error { var resolvedAndRoot [2]string if err := json.Unmarshal(data, &resolvedAndRoot); err != nil { return fmt.Errorf("invalid BuildInfoResolvedRoot: %s", data) } *b = readableBuildInfoResolvedRoot{ Resolved: resolvedAndRoot[0], Root: resolvedAndRoot[1], } return nil } func toReadableBuildInfo(buildInfo *incremental.BuildInfo, buildInfoText string) string { readable := readableBuildInfo{ buildInfo: buildInfo, Version: buildInfo.Version, Errors: buildInfo.Errors, CheckPending: buildInfo.CheckPending, FileNames: buildInfo.FileNames, Options: buildInfo.Options, LatestChangedDtsFile: buildInfo.LatestChangedDtsFile, SemanticErrors: buildInfo.SemanticErrors, Size: len(buildInfoText), } readable.setFileInfos() readable.setRoot() readable.setFileIdsList() readable.setReferencedMap() readable.setChangeFileSet() readable.setSemanticDiagnostics() readable.setEmitDiagnostics() readable.setAffectedFilesPendingEmit() readable.setEmitSignatures() readable.setResolvedRoot() contents, err := jsonutil.MarshalIndent(&readable, "", " ") if err != nil { panic("readableBuildInfo: failed to marshal readable build info: " + err.Error()) } return string(contents) } func (r *readableBuildInfo) toFilePath(fileId incremental.BuildInfoFileId) string { return r.buildInfo.FileNames[fileId-1] } func (r *readableBuildInfo) toFilePathSet(fileIdListId incremental.BuildInfoFileIdListId) []string { return r.FileIdsList[fileIdListId-1] } func (r *readableBuildInfo) toReadableBuildInfoDiagnostic(diagnostics []*incremental.BuildInfoDiagnostic) []*readableBuildInfoDiagnostic { return core.Map(diagnostics, func(d *incremental.BuildInfoDiagnostic) *readableBuildInfoDiagnostic { var file string if d.File != 0 { file = r.toFilePath(d.File) } return &readableBuildInfoDiagnostic{ File: file, NoFile: d.NoFile, Pos: d.Pos, End: d.End, Code: d.Code, Category: d.Category, Message: d.Message, MessageChain: r.toReadableBuildInfoDiagnostic(d.MessageChain), RelatedInformation: r.toReadableBuildInfoDiagnostic(d.RelatedInformation), ReportsUnnecessary: d.ReportsUnnecessary, ReportsDeprecated: d.ReportsDeprecated, SkippedOnNoEmit: d.SkippedOnNoEmit, } }) } func (r *readableBuildInfo) toReadableBuildInfoDiagnosticsOfFile(diagnostics *incremental.BuildInfoDiagnosticsOfFile) *readableBuildInfoDiagnosticsOfFile { return &readableBuildInfoDiagnosticsOfFile{ file: r.toFilePath(diagnostics.FileId), diagnostics: r.toReadableBuildInfoDiagnostic(diagnostics.Diagnostics), } } func (r *readableBuildInfo) setFileInfos() { r.FileInfos = core.MapIndex(r.buildInfo.FileInfos, func(original *incremental.BuildInfoFileInfo, index int) *readableBuildInfoFileInfo { fileInfo := original.GetFileInfo() // Dont set original for string encoding if original.HasSignature() { original = nil } return &readableBuildInfoFileInfo{ FileName: r.toFilePath(incremental.BuildInfoFileId(index + 1)), Version: fileInfo.Version(), Signature: fileInfo.Signature(), AffectsGlobalScope: fileInfo.AffectsGlobalScope(), ImpliedNodeFormat: fileInfo.ImpliedNodeFormat().String(), Original: original, } }) } func (r *readableBuildInfo) setRoot() { r.Root = core.Map(r.buildInfo.Root, func(original *incremental.BuildInfoRoot) *readableBuildInfoRoot { var files []string if original.NonIncremental != "" { files = []string{original.NonIncremental} } else if original.End == 0 { files = []string{r.toFilePath(original.Start)} } else { files = make([]string, 0, original.End-original.Start+1) for i := original.Start; i <= original.End; i++ { files = append(files, r.toFilePath(i)) } } return &readableBuildInfoRoot{ Files: files, Original: original, } }) } func (r *readableBuildInfo) setFileIdsList() { r.FileIdsList = core.Map(r.buildInfo.FileIdsList, func(ids []incremental.BuildInfoFileId) []string { return core.Map(ids, r.toFilePath) }) } func (r *readableBuildInfo) setReferencedMap() { if r.buildInfo.ReferencedMap != nil { r.ReferencedMap = &collections.OrderedMap[string, []string]{} for _, entry := range r.buildInfo.ReferencedMap { r.ReferencedMap.Set(r.toFilePath(entry.FileId), r.toFilePathSet(entry.FileIdListId)) } } } func (r *readableBuildInfo) setChangeFileSet() { r.ChangeFileSet = core.Map(r.buildInfo.ChangeFileSet, r.toFilePath) } func (r *readableBuildInfo) setSemanticDiagnostics() { r.SemanticDiagnosticsPerFile = core.Map(r.buildInfo.SemanticDiagnosticsPerFile, func(diagnostics *incremental.BuildInfoSemanticDiagnostic) *readableBuildInfoSemanticDiagnostic { if diagnostics.FileId != 0 { return &readableBuildInfoSemanticDiagnostic{ file: r.toFilePath(diagnostics.FileId), } } return &readableBuildInfoSemanticDiagnostic{ diagnostics: r.toReadableBuildInfoDiagnosticsOfFile(diagnostics.Diagnostics), } }) } func (r *readableBuildInfo) setEmitDiagnostics() { r.EmitDiagnosticsPerFile = core.Map(r.buildInfo.EmitDiagnosticsPerFile, r.toReadableBuildInfoDiagnosticsOfFile) } func (r *readableBuildInfo) setAffectedFilesPendingEmit() { if r.buildInfo.AffectedFilesPendingEmit == nil { return } fullEmitKind := incremental.GetFileEmitKind(r.buildInfo.GetCompilerOptions("")) r.AffectedFilesPendingEmit = core.Map(r.buildInfo.AffectedFilesPendingEmit, func(pendingEmit *incremental.BuildInfoFilePendingEmit) *readableBuildInfoFilePendingEmit { emitKind := core.IfElse(pendingEmit.EmitKind == 0, fullEmitKind, pendingEmit.EmitKind) return &readableBuildInfoFilePendingEmit{ file: r.toFilePath(pendingEmit.FileId), emitKind: toReadableFileEmitKind(emitKind), original: pendingEmit, } }) } func toReadableFileEmitKind(fileEmitKind incremental.FileEmitKind) string { var builder strings.Builder addFlags := func(flags string) { if builder.Len() == 0 { builder.WriteString(flags) } else { builder.WriteString("|") builder.WriteString(flags) } } if fileEmitKind != 0 { if (fileEmitKind & incremental.FileEmitKindJs) != 0 { addFlags("Js") } if (fileEmitKind & incremental.FileEmitKindJsMap) != 0 { addFlags("JsMap") } if (fileEmitKind & incremental.FileEmitKindJsInlineMap) != 0 { addFlags("JsInlineMap") } if (fileEmitKind & incremental.FileEmitKindDts) == incremental.FileEmitKindDts { addFlags("Dts") } else { if (fileEmitKind & incremental.FileEmitKindDtsEmit) != 0 { addFlags("DtsEmit") } if (fileEmitKind & incremental.FileEmitKindDtsErrors) != 0 { addFlags("DtsErrors") } } if (fileEmitKind & incremental.FileEmitKindDtsMap) != 0 { addFlags("DtsMap") } } if builder.Len() != 0 { return builder.String() } return "None" } func (r *readableBuildInfo) setEmitSignatures() { r.EmitSignatures = core.Map(r.buildInfo.EmitSignatures, func(signature *incremental.BuildInfoEmitSignature) *readableBuildInfoEmitSignature { return &readableBuildInfoEmitSignature{ File: r.toFilePath(signature.FileId), Signature: signature.Signature, DiffersOnlyInDtsMap: signature.DiffersOnlyInDtsMap, DiffersInOptions: signature.DiffersInOptions, Original: signature, } }) } func (r *readableBuildInfo) setResolvedRoot() { r.ResolvedRoot = core.Map(r.buildInfo.ResolvedRoot, func(original *incremental.BuildInfoResolvedRoot) *readableBuildInfoResolvedRoot { return &readableBuildInfoResolvedRoot{ Resolved: r.toFilePath(original.Resolved), Root: r.toFilePath(original.Root), } }) }