2025-10-15 10:12:44 +03:00

273 lines
9.6 KiB
Go

package tsbaseline
import (
"slices"
"strings"
"testing"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/collections"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/parser"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/testutil/baseline"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/testutil/harnessutil"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath"
)
func DoJSEmitBaseline(
t *testing.T,
baselinePath string,
header string,
options *core.CompilerOptions,
result *harnessutil.CompilationResult,
tsConfigFiles []*harnessutil.TestFile,
toBeCompiled []*harnessutil.TestFile,
otherFiles []*harnessutil.TestFile,
harnessSettings *harnessutil.HarnessOptions,
opts baseline.Options,
) {
if !options.NoEmit.IsTrue() && !options.EmitDeclarationOnly.IsTrue() && result.JS.Size() == 0 && len(result.Diagnostics) == 0 {
t.Fatal("Expected at least one js file to be emitted or at least one error to be created.")
}
// check js output
var tsCode strings.Builder
tsSources := core.Concatenate(otherFiles, toBeCompiled)
tsCode.WriteString("//// [")
tsCode.WriteString(header)
tsCode.WriteString("] ////\r\n\r\n")
for i, file := range tsSources {
tsCode.WriteString("//// [")
tsCode.WriteString(tspath.GetBaseFileName(file.UnitName))
tsCode.WriteString("]\r\n")
tsCode.WriteString(file.Content)
if i < len(tsSources)-1 {
tsCode.WriteString("\r\n")
}
}
var jsCode strings.Builder
for file := range result.JS.Values() {
if jsCode.Len() > 0 && !strings.HasSuffix(jsCode.String(), "\n") {
jsCode.WriteString("\r\n")
}
if len(result.Diagnostics) == 0 && strings.HasSuffix(file.UnitName, tspath.ExtensionJson) {
fileParseResult := parser.ParseSourceFile(ast.SourceFileParseOptions{
FileName: file.UnitName,
Path: tspath.Path(file.UnitName),
CompilerOptions: options.SourceFileAffecting(),
}, file.Content, core.ScriptKindJSON)
if len(fileParseResult.Diagnostics()) > 0 {
jsCode.WriteString(getErrorBaseline(t, []*harnessutil.TestFile{file}, fileParseResult.Diagnostics(), false /*pretty*/))
continue
}
}
jsCode.WriteString(fileOutput(file, harnessSettings))
}
if result.DTS.Size() > 0 {
jsCode.WriteString("\r\n\r\n")
for declFile := range result.DTS.Values() {
jsCode.WriteString(fileOutput(declFile, harnessSettings))
}
}
declFileContext := prepareDeclarationCompilationContext(
toBeCompiled,
otherFiles,
result,
harnessSettings,
options,
"", /*currentDirectory*/
)
declFileCompilationResult := compileDeclarationFiles(t, declFileContext, result.Symlinks)
if declFileCompilationResult != nil && len(declFileCompilationResult.declResult.Diagnostics) > 0 {
jsCode.WriteString("\r\n\r\n//// [DtsFileErrors]\r\n")
jsCode.WriteString("\r\n\r\n")
jsCode.WriteString(getErrorBaseline(
t,
slices.Concat(tsConfigFiles, declFileCompilationResult.declInputFiles, declFileCompilationResult.declOtherFiles),
declFileCompilationResult.declResult.Diagnostics,
false, /*pretty*/
))
}
if !options.NoCheck.IsTrue() && !options.NoEmit.IsTrue() {
testConfig := make(map[string]string)
testConfig["noCheck"] = "true"
withoutChecking := result.Repeat(testConfig)
compareResultFileSets := func(a *collections.OrderedMap[string, *harnessutil.TestFile], b *collections.OrderedMap[string, *harnessutil.TestFile]) {
for key, doc := range a.Entries() {
original := b.GetOrZero(key)
if original == nil {
jsCode.WriteString("\r\n\r\n!!!! File ")
jsCode.WriteString(removeTestPathPrefixes(doc.UnitName, false /*retainTrailingDirectorySeparator*/))
jsCode.WriteString(" missing from original emit, but present in noCheck emit\r\n")
jsCode.WriteString(fileOutput(doc, harnessSettings))
} else if original.Content != doc.Content {
jsCode.WriteString("\r\n\r\n!!!! File ")
jsCode.WriteString(removeTestPathPrefixes(doc.UnitName, false /*retainTrailingDirectorySeparator*/))
jsCode.WriteString(" differs from original emit in noCheck emit\r\n")
var fileName string
if harnessSettings.FullEmitPaths {
fileName = removeTestPathPrefixes(doc.UnitName, false /*retainTrailingDirectorySeparator*/)
} else {
fileName = tspath.GetBaseFileName(doc.UnitName)
}
jsCode.WriteString("//// [")
jsCode.WriteString(fileName)
jsCode.WriteString("]\r\n")
expected := original.Content
actual := doc.Content
jsCode.WriteString(baseline.DiffText("Expected\tThe full check baseline", "Actual\twith noCheck set", expected, actual))
}
}
}
compareResultFileSets(&withoutChecking.DTS, &result.DTS)
compareResultFileSets(&withoutChecking.JS, &result.JS)
}
if tspath.FileExtensionIsOneOf(baselinePath, []string{tspath.ExtensionTs, tspath.ExtensionTsx}) {
baselinePath = tspath.ChangeExtension(baselinePath, tspath.ExtensionJs)
}
var actual string
if jsCode.Len() > 0 {
actual = tsCode.String() + "\r\n\r\n" + jsCode.String()
} else {
actual = baseline.NoContent
}
baseline.Run(t, baselinePath, actual, opts)
}
func fileOutput(file *harnessutil.TestFile, settings *harnessutil.HarnessOptions) string {
var fileName string
if settings.FullEmitPaths {
fileName = removeTestPathPrefixes(file.UnitName, false /*retainTrailingDirectorySeparator*/)
} else {
fileName = tspath.GetBaseFileName(file.UnitName)
}
return "//// [" + fileName + "]\r\n" + removeTestPathPrefixes(file.Content, false /*retainTrailingDirectorySeparator*/)
}
type declarationCompilationContext struct {
declInputFiles []*harnessutil.TestFile
declOtherFiles []*harnessutil.TestFile
harnessSettings *harnessutil.HarnessOptions
options *core.CompilerOptions
currentDirectory string
}
func prepareDeclarationCompilationContext(
inputFiles []*harnessutil.TestFile,
otherFiles []*harnessutil.TestFile,
result *harnessutil.CompilationResult,
harnessSettings *harnessutil.HarnessOptions,
options *core.CompilerOptions,
// Current directory is needed for rwcRunner to be able to use currentDirectory defined in json file
currentDirectory string,
) *declarationCompilationContext {
if options.Declaration.IsTrue() && len(result.Diagnostics) == 0 {
if options.EmitDeclarationOnly.IsTrue() {
if result.JS.Size() > 0 || (result.DTS.Size() == 0 && !options.NoEmit.IsTrue()) {
panic("Only declaration files should be generated when emitDeclarationOnly:true")
}
} else if result.DTS.Size() != result.GetNumberOfJSFiles(false /*includeJson*/) {
panic("There were no errors and declFiles generated did not match number of js files generated")
}
}
var declInputFiles []*harnessutil.TestFile
var declOtherFiles []*harnessutil.TestFile
findUnit := func(fileName string, units []*harnessutil.TestFile) *harnessutil.TestFile {
for _, unit := range units {
if unit.UnitName == fileName {
return unit
}
}
return nil
}
findResultCodeFile := func(fileName string) *harnessutil.TestFile {
sourceFile := result.Program.GetSourceFile(fileName)
if sourceFile == nil {
panic("Program has no source file with name '" + fileName + "'")
}
// Is this file going to be emitted separately
var sourceFileName string
if len(options.OutDir) != 0 {
sourceFilePath := tspath.GetNormalizedAbsolutePath(sourceFile.FileName(), result.Program.GetCurrentDirectory())
sourceFilePath = strings.Replace(sourceFilePath, result.Program.CommonSourceDirectory(), "", 1)
sourceFileName = tspath.CombinePaths(options.OutDir, sourceFilePath)
} else {
sourceFileName = sourceFile.FileName()
}
dTsFileName := tspath.RemoveFileExtension(sourceFileName) + tspath.GetDeclarationEmitExtensionForPath(sourceFileName)
return result.DTS.GetOrZero(dTsFileName)
}
addDtsFile := func(file *harnessutil.TestFile, dtsFiles []*harnessutil.TestFile) []*harnessutil.TestFile {
if tspath.IsDeclarationFileName(file.UnitName) || tspath.HasJSONFileExtension(file.UnitName) {
dtsFiles = append(dtsFiles, file)
} else if tspath.HasTSFileExtension(file.UnitName) || (tspath.HasJSFileExtension(file.UnitName) && options.GetAllowJS()) {
declFile := findResultCodeFile(file.UnitName)
if declFile != nil && findUnit(declFile.UnitName, declInputFiles) == nil && findUnit(declFile.UnitName, declOtherFiles) == nil {
dtsFiles = append(dtsFiles, &harnessutil.TestFile{
UnitName: declFile.UnitName,
Content: strings.TrimPrefix(declFile.Content, "\uFEFF"),
})
}
}
return dtsFiles
}
// if the .d.ts is non-empty, confirm it compiles correctly as well
if options.Declaration.IsTrue() && len(result.Diagnostics) == 0 && result.DTS.Size() > 0 {
for _, file := range inputFiles {
declInputFiles = addDtsFile(file, declInputFiles)
}
for _, file := range otherFiles {
declOtherFiles = addDtsFile(file, declOtherFiles)
}
return &declarationCompilationContext{
declInputFiles,
declOtherFiles,
harnessSettings,
options,
core.IfElse(len(currentDirectory) > 0, currentDirectory, harnessSettings.CurrentDirectory),
}
}
return nil
}
type declarationCompilationResult struct {
declInputFiles []*harnessutil.TestFile
declOtherFiles []*harnessutil.TestFile
declResult *harnessutil.CompilationResult
}
func compileDeclarationFiles(t *testing.T, context *declarationCompilationContext, symlinks map[string]string) *declarationCompilationResult {
if context == nil {
return nil
}
declFileCompilationResult := harnessutil.CompileFilesEx(t,
context.declInputFiles,
context.declOtherFiles,
context.harnessSettings,
context.options,
context.currentDirectory,
symlinks,
nil)
return &declarationCompilationResult{
context.declInputFiles,
context.declOtherFiles,
declFileCompilationResult,
}
}