From b7ee86ab1d42893ca4ca6ae128099d290d4901e7 Mon Sep 17 00:00:00 2001 From: Egor Aristov Date: Wed, 15 Oct 2025 19:22:20 +0300 Subject: [PATCH] remove unused packages --- .../internal/tsgo/execute/build/buildtask.go | 855 ---- .../tsgo/execute/build/compilerHost.go | 40 - .../internal/tsgo/execute/build/graph_test.go | 153 - kitcom/internal/tsgo/execute/build/host.go | 113 - .../tsgo/execute/build/orchestrator.go | 400 -- .../internal/tsgo/execute/build/parseCache.go | 48 - .../tsgo/execute/build/uptodatestatus.go | 133 - .../incremental/affectedfileshandler.go | 384 -- .../tsgo/execute/incremental/buildInfo.go | 584 --- .../incremental/buildinfotosnapshot.go | 171 - .../execute/incremental/emitfileshandler.go | 329 -- .../internal/tsgo/execute/incremental/host.go | 39 - .../tsgo/execute/incremental/incremental.go | 56 - .../tsgo/execute/incremental/program.go | 364 -- .../execute/incremental/programtosnapshot.go | 300 -- .../tsgo/execute/incremental/referencemap.go | 52 - .../tsgo/execute/incremental/snapshot.go | 323 -- .../incremental/snapshottobuildinfo.go | 363 -- kitcom/internal/tsgo/execute/tsc.go | 319 -- kitcom/internal/tsgo/execute/tsc/compile.go | 78 - .../internal/tsgo/execute/tsc/diagnostics.go | 166 - kitcom/internal/tsgo/execute/tsc/emit.go | 142 - .../tsgo/execute/tsc/extendedconfigcache.go | 45 - kitcom/internal/tsgo/execute/tsc/help.go | 394 -- .../internal/tsgo/execute/tsc/statistics.go | 157 - kitcom/internal/tsgo/execute/tsctests/fs.go | 86 - .../execute/tsctests/readablebuildinfo.go | 422 -- .../internal/tsgo/execute/tsctests/runner.go | 192 - kitcom/internal/tsgo/execute/tsctests/sys.go | 627 --- .../tsgo/execute/tsctests/tsc_test.go | 3989 ----------------- .../tsgo/execute/tsctests/tscbuild_test.go | 3934 ---------------- .../tsgo/execute/tsctests/tscwatch_test.go | 131 - kitcom/internal/tsgo/execute/watcher.go | 160 - kitcom/internal/tsgo/pprof/pprof.go | 63 - 34 files changed, 15612 deletions(-) delete mode 100644 kitcom/internal/tsgo/execute/build/buildtask.go delete mode 100644 kitcom/internal/tsgo/execute/build/compilerHost.go delete mode 100644 kitcom/internal/tsgo/execute/build/graph_test.go delete mode 100644 kitcom/internal/tsgo/execute/build/host.go delete mode 100644 kitcom/internal/tsgo/execute/build/orchestrator.go delete mode 100644 kitcom/internal/tsgo/execute/build/parseCache.go delete mode 100644 kitcom/internal/tsgo/execute/build/uptodatestatus.go delete mode 100644 kitcom/internal/tsgo/execute/incremental/affectedfileshandler.go delete mode 100644 kitcom/internal/tsgo/execute/incremental/buildInfo.go delete mode 100644 kitcom/internal/tsgo/execute/incremental/buildinfotosnapshot.go delete mode 100644 kitcom/internal/tsgo/execute/incremental/emitfileshandler.go delete mode 100644 kitcom/internal/tsgo/execute/incremental/host.go delete mode 100644 kitcom/internal/tsgo/execute/incremental/incremental.go delete mode 100644 kitcom/internal/tsgo/execute/incremental/program.go delete mode 100644 kitcom/internal/tsgo/execute/incremental/programtosnapshot.go delete mode 100644 kitcom/internal/tsgo/execute/incremental/referencemap.go delete mode 100644 kitcom/internal/tsgo/execute/incremental/snapshot.go delete mode 100644 kitcom/internal/tsgo/execute/incremental/snapshottobuildinfo.go delete mode 100644 kitcom/internal/tsgo/execute/tsc.go delete mode 100644 kitcom/internal/tsgo/execute/tsc/compile.go delete mode 100644 kitcom/internal/tsgo/execute/tsc/diagnostics.go delete mode 100644 kitcom/internal/tsgo/execute/tsc/emit.go delete mode 100644 kitcom/internal/tsgo/execute/tsc/extendedconfigcache.go delete mode 100644 kitcom/internal/tsgo/execute/tsc/help.go delete mode 100644 kitcom/internal/tsgo/execute/tsc/statistics.go delete mode 100644 kitcom/internal/tsgo/execute/tsctests/fs.go delete mode 100644 kitcom/internal/tsgo/execute/tsctests/readablebuildinfo.go delete mode 100644 kitcom/internal/tsgo/execute/tsctests/runner.go delete mode 100644 kitcom/internal/tsgo/execute/tsctests/sys.go delete mode 100644 kitcom/internal/tsgo/execute/tsctests/tsc_test.go delete mode 100644 kitcom/internal/tsgo/execute/tsctests/tscbuild_test.go delete mode 100644 kitcom/internal/tsgo/execute/tsctests/tscwatch_test.go delete mode 100644 kitcom/internal/tsgo/execute/watcher.go delete mode 100644 kitcom/internal/tsgo/pprof/pprof.go diff --git a/kitcom/internal/tsgo/execute/build/buildtask.go b/kitcom/internal/tsgo/execute/build/buildtask.go deleted file mode 100644 index 7ae8f25..0000000 --- a/kitcom/internal/tsgo/execute/build/buildtask.go +++ /dev/null @@ -1,855 +0,0 @@ -package build - -import ( - "fmt" - "slices" - "strings" - "sync" - "sync/atomic" - "time" - - "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/diagnostics" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/execute/incremental" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/execute/tsc" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tsoptions" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath" -) - -type updateKind uint - -const ( - updateKindNone updateKind = iota - updateKindConfig - updateKindUpdate -) - -type buildKind uint - -const ( - buildKindNone buildKind = iota - buildKindPseudo - buildKindProgram -) - -type upstreamTask struct { - task *buildTask - refIndex int -} -type buildInfoEntry struct { - buildInfo *incremental.BuildInfo - path tspath.Path - mTime time.Time - dtsTime *time.Time -} - -type taskResult struct { - builder strings.Builder - reportStatus tsc.DiagnosticReporter - diagnosticReporter tsc.DiagnosticReporter - exitStatus tsc.ExitStatus - statistics *tsc.Statistics - program *incremental.Program - buildKind buildKind - filesToDelete []string -} - -type buildTask struct { - config string - resolved *tsoptions.ParsedCommandLine - upStream []*upstreamTask - downStream []*buildTask // Only set and used in watch mode - status *upToDateStatus - done chan struct{} - - // task reporting - result *taskResult - prevReporter *buildTask - reportDone chan struct{} - - // Watching things - configTime time.Time - extendedConfigTimes []time.Time - inputFiles []time.Time - - buildInfoEntry *buildInfoEntry - buildInfoEntryMu sync.Mutex - - errors []*ast.Diagnostic - pending atomic.Bool - isInitialCycle bool - downStreamUpdateMu sync.Mutex - dirty bool -} - -func (t *buildTask) waitOnUpstream() { - for _, upstream := range t.upStream { - <-upstream.task.done - } -} - -func (t *buildTask) unblockDownstream() { - t.pending.Store(false) - t.isInitialCycle = false - close(t.done) -} - -func (t *buildTask) reportDiagnostic(err *ast.Diagnostic) { - t.errors = append(t.errors, err) - t.result.diagnosticReporter(err) -} - -func (t *buildTask) report(orchestrator *Orchestrator, configPath tspath.Path, buildResult *orchestratorResult) { - if t.prevReporter != nil { - <-t.prevReporter.reportDone - } - if len(t.errors) > 0 { - buildResult.errors = append(core.IfElse(buildResult.errors != nil, buildResult.errors, []*ast.Diagnostic{}), t.errors...) - } - fmt.Fprint(orchestrator.opts.Sys.Writer(), t.result.builder.String()) - if t.result.exitStatus > buildResult.result.Status { - buildResult.result.Status = t.result.exitStatus - } - if t.result.statistics != nil { - buildResult.statistics.Aggregate(t.result.statistics) - } - // If we built the program, or updated timestamps, or had errors, we need to - // delete files that are no longer needed - switch t.result.buildKind { - case buildKindProgram: - if orchestrator.opts.Testing != nil { - orchestrator.opts.Testing.OnProgram(t.result.program) - } - buildResult.statistics.ProjectsBuilt++ - case buildKindPseudo: - buildResult.statistics.TimestampUpdates++ - } - buildResult.filesToDelete = append(buildResult.filesToDelete, t.result.filesToDelete...) - t.result = nil - close(t.reportDone) -} - -func (t *buildTask) buildProject(orchestrator *Orchestrator, path tspath.Path) { - // Wait on upstream tasks to complete - t.waitOnUpstream() - if t.pending.Load() { - t.status = t.getUpToDateStatus(orchestrator, path) - t.reportUpToDateStatus(orchestrator) - if !t.handleStatusThatDoesntRequireBuild(orchestrator) { - t.compileAndEmit(orchestrator, path) - t.updateDownstream(orchestrator, path) - } else { - if t.resolved != nil { - for _, diagnostic := range t.resolved.GetConfigFileParsingDiagnostics() { - t.reportDiagnostic(diagnostic) - } - } - if len(t.errors) > 0 { - t.result.exitStatus = tsc.ExitStatusDiagnosticsPresent_OutputsSkipped - } - } - } else { - if len(t.errors) > 0 { - t.reportUpToDateStatus(orchestrator) - for _, err := range t.errors { - // Should not add the diagnostics so just reporting - t.result.diagnosticReporter(err) - } - } - } - t.unblockDownstream() -} - -func (t *buildTask) updateDownstream(orchestrator *Orchestrator, path tspath.Path) { - if t.isInitialCycle { - return - } - if orchestrator.opts.Command.BuildOptions.StopBuildOnErrors.IsTrue() && t.status.isError() { - return - } - - for _, downStream := range t.downStream { - downStream.downStreamUpdateMu.Lock() - if downStream.status != nil { - switch downStream.status.kind { - case upToDateStatusTypeUpToDate: - if !t.result.program.HasChangedDtsFile() { - downStream.status = &upToDateStatus{kind: upToDateStatusTypeUpToDateWithUpstreamTypes, data: downStream.status.data} - break - } - fallthrough - case upToDateStatusTypeUpToDateWithUpstreamTypes, - upToDateStatusTypeUpToDateWithInputFileText: - if t.result.program.HasChangedDtsFile() { - downStream.status = &upToDateStatus{kind: upToDateStatusTypeInputFileNewer, data: &inputOutputName{t.config, downStream.status.oldestOutputFileName()}} - } - case upToDateStatusTypeUpstreamErrors: - upstreamErrors := downStream.status.upstreamErrors() - refConfig := core.ResolveConfigFileNameOfProjectReference(upstreamErrors.ref) - if orchestrator.toPath(refConfig) == path { - downStream.resetStatus() - } - } - } - downStream.pending.Store(true) - downStream.downStreamUpdateMu.Unlock() - } -} - -func (t *buildTask) compileAndEmit(orchestrator *Orchestrator, path tspath.Path) { - t.errors = nil - if orchestrator.opts.Command.BuildOptions.Verbose.IsTrue() { - t.result.reportStatus(ast.NewCompilerDiagnostic(diagnostics.Building_project_0, orchestrator.relativeFileName(t.config))) - } - - // Real build - var compileTimes tsc.CompileTimes - configTime, _ := orchestrator.host.configTimes.Load(path) - compileTimes.ConfigTime = configTime - buildInfoReadStart := orchestrator.opts.Sys.Now() - var oldProgram *incremental.Program - if !orchestrator.opts.Command.BuildOptions.Force.IsTrue() { - oldProgram = incremental.ReadBuildInfoProgram(t.resolved, orchestrator.host, orchestrator.host) - } - compileTimes.BuildInfoReadTime = orchestrator.opts.Sys.Now().Sub(buildInfoReadStart) - parseStart := orchestrator.opts.Sys.Now() - program := compiler.NewProgram(compiler.ProgramOptions{ - Config: t.resolved, - Host: &compilerHost{ - host: orchestrator.host, - trace: tsc.GetTraceWithWriterFromSys(&t.result.builder, orchestrator.opts.Testing), - }, - JSDocParsingMode: ast.JSDocParsingModeParseForTypeErrors, - }) - compileTimes.ParseTime = orchestrator.opts.Sys.Now().Sub(parseStart) - changesComputeStart := orchestrator.opts.Sys.Now() - t.result.program = incremental.NewProgram(program, oldProgram, orchestrator.host, orchestrator.opts.Testing != nil) - compileTimes.ChangesComputeTime = orchestrator.opts.Sys.Now().Sub(changesComputeStart) - - result, statistics := tsc.EmitAndReportStatistics(tsc.EmitInput{ - Sys: orchestrator.opts.Sys, - ProgramLike: t.result.program, - Program: program, - Config: t.resolved, - ReportDiagnostic: t.reportDiagnostic, - ReportErrorSummary: tsc.QuietDiagnosticsReporter, - Writer: &t.result.builder, - WriteFile: func(fileName, text string, writeByteOrderMark bool, data *compiler.WriteFileData) error { - return t.writeFile(orchestrator, fileName, text, writeByteOrderMark, data) - }, - CompileTimes: &compileTimes, - Testing: orchestrator.opts.Testing, - TestingMTimesCache: orchestrator.host.mTimes, - }) - t.result.exitStatus = result.Status - t.result.statistics = statistics - if (!program.Options().NoEmitOnError.IsTrue() || len(result.Diagnostics) == 0) && - (len(result.EmitResult.EmittedFiles) > 0 || t.status.kind != upToDateStatusTypeOutOfDateBuildInfoWithErrors) { - // Update time stamps for rest of the outputs - t.updateTimeStamps(orchestrator, result.EmitResult.EmittedFiles, diagnostics.Updating_unchanged_output_timestamps_of_project_0) - } - t.result.buildKind = buildKindProgram - if result.Status == tsc.ExitStatusDiagnosticsPresent_OutputsSkipped || result.Status == tsc.ExitStatusDiagnosticsPresent_OutputsGenerated { - t.status = &upToDateStatus{kind: upToDateStatusTypeBuildErrors} - } else { - var oldestOutputFileName string - if len(result.EmitResult.EmittedFiles) > 0 { - oldestOutputFileName = result.EmitResult.EmittedFiles[0] - } else { - oldestOutputFileName = core.FirstOrNilSeq(t.resolved.GetOutputFileNames()) - } - t.status = &upToDateStatus{kind: upToDateStatusTypeUpToDate, data: oldestOutputFileName} - } -} - -func (t *buildTask) handleStatusThatDoesntRequireBuild(orchestrator *Orchestrator) bool { - switch t.status.kind { - case upToDateStatusTypeUpToDate: - if orchestrator.opts.Command.BuildOptions.Dry.IsTrue() { - t.result.reportStatus(ast.NewCompilerDiagnostic(diagnostics.Project_0_is_up_to_date, t.config)) - } - return true - case upToDateStatusTypeUpstreamErrors: - upstreamStatus := t.status.upstreamErrors() - if orchestrator.opts.Command.BuildOptions.Verbose.IsTrue() { - t.result.reportStatus(ast.NewCompilerDiagnostic( - core.IfElse( - upstreamStatus.refHasUpstreamErrors, - diagnostics.Skipping_build_of_project_0_because_its_dependency_1_was_not_built, - diagnostics.Skipping_build_of_project_0_because_its_dependency_1_has_errors, - ), - orchestrator.relativeFileName(t.config), - orchestrator.relativeFileName(upstreamStatus.ref), - )) - } - return true - case upToDateStatusTypeSolution: - return true - case upToDateStatusTypeConfigFileNotFound: - t.reportDiagnostic(ast.NewCompilerDiagnostic(diagnostics.File_0_not_found, t.config)) - return true - } - - // update timestamps - if t.status.isPseudoBuild() { - if orchestrator.opts.Command.BuildOptions.Dry.IsTrue() { - t.result.reportStatus(ast.NewCompilerDiagnostic(diagnostics.A_non_dry_build_would_update_timestamps_for_output_of_project_0, t.config)) - t.status = &upToDateStatus{kind: upToDateStatusTypeUpToDate} - return true - } - - t.updateTimeStamps(orchestrator, nil, diagnostics.Updating_output_timestamps_of_project_0) - t.status = &upToDateStatus{kind: upToDateStatusTypeUpToDate, data: t.status.data} - t.result.buildKind = buildKindPseudo - return true - } - - if orchestrator.opts.Command.BuildOptions.Dry.IsTrue() { - t.result.reportStatus(ast.NewCompilerDiagnostic(diagnostics.A_non_dry_build_would_build_project_0, t.config)) - t.status = &upToDateStatus{kind: upToDateStatusTypeUpToDate} - return true - } - return false -} - -func (t *buildTask) getUpToDateStatus(orchestrator *Orchestrator, configPath tspath.Path) *upToDateStatus { - if t.status != nil { - return t.status - } - // Config file not found - if t.resolved == nil { - return &upToDateStatus{kind: upToDateStatusTypeConfigFileNotFound} - } - - // Solution - nothing to build - if len(t.resolved.FileNames()) == 0 && t.resolved.ProjectReferences() != nil { - return &upToDateStatus{kind: upToDateStatusTypeSolution} - } - - for _, upstream := range t.upStream { - if orchestrator.opts.Command.BuildOptions.StopBuildOnErrors.IsTrue() && upstream.task.status.isError() { - // Upstream project has errors, so we cannot build this project - return &upToDateStatus{kind: upToDateStatusTypeUpstreamErrors, data: &upstreamErrors{t.resolved.ProjectReferences()[upstream.refIndex].Path, upstream.task.status.kind == upToDateStatusTypeUpstreamErrors}} - } - } - - if orchestrator.opts.Command.BuildOptions.Force.IsTrue() { - return &upToDateStatus{kind: upToDateStatusTypeForceBuild} - } - - // Check the build info - buildInfoPath := t.resolved.GetBuildInfoFileName() - buildInfo, buildInfoTime := t.loadOrStoreBuildInfo(orchestrator, configPath, buildInfoPath) - if buildInfo == nil { - return &upToDateStatus{kind: upToDateStatusTypeOutputMissing, data: buildInfoPath} - } - - // build info version - if !buildInfo.IsValidVersion() { - return &upToDateStatus{kind: upToDateStatusTypeTsVersionOutputOfDate, data: buildInfo.Version} - } - - // Report errors if build info indicates errors - if buildInfo.Errors || // Errors that need to be reported irrespective of "--noCheck" - (!t.resolved.CompilerOptions().NoCheck.IsTrue() && (buildInfo.SemanticErrors || buildInfo.CheckPending)) { // Errors without --noCheck - return &upToDateStatus{kind: upToDateStatusTypeOutOfDateBuildInfoWithErrors, data: buildInfoPath} - } - - if t.resolved.CompilerOptions().IsIncremental() { - if !buildInfo.IsIncremental() { - // Program options out of date - return &upToDateStatus{kind: upToDateStatusTypeOutOfDateOptions, data: buildInfoPath} - } - - // Errors need to be reported if build info has errors - if (t.resolved.CompilerOptions().GetEmitDeclarations() && buildInfo.EmitDiagnosticsPerFile != nil) || // Always reported errors - (!t.resolved.CompilerOptions().NoCheck.IsTrue() && // Semantic errors if not --noCheck - (buildInfo.ChangeFileSet != nil || buildInfo.SemanticDiagnosticsPerFile != nil)) { - return &upToDateStatus{kind: upToDateStatusTypeOutOfDateBuildInfoWithErrors, data: buildInfoPath} - } - - // Pending emit files - if !t.resolved.CompilerOptions().NoEmit.IsTrue() && - (buildInfo.ChangeFileSet != nil || buildInfo.AffectedFilesPendingEmit != nil) { - return &upToDateStatus{kind: upToDateStatusTypeOutOfDateBuildInfoWithPendingEmit, data: buildInfoPath} - } - - // Some of the emit files like source map or dts etc are not yet done - if buildInfo.IsEmitPending(t.resolved, tspath.GetDirectoryPath(tspath.GetNormalizedAbsolutePath(buildInfoPath, orchestrator.comparePathsOptions.CurrentDirectory))) { - return &upToDateStatus{kind: upToDateStatusTypeOutOfDateOptions, data: buildInfoPath} - } - } - var inputTextUnchanged bool - oldestOutputFileAndTime := fileAndTime{buildInfoPath, buildInfoTime} - var newestInputFileAndTime fileAndTime - var seenRoots collections.Set[tspath.Path] - var buildInfoRootInfoReader *incremental.BuildInfoRootInfoReader - for _, inputFile := range t.resolved.FileNames() { - inputTime := orchestrator.host.GetMTime(inputFile) - if inputTime.IsZero() { - return &upToDateStatus{kind: upToDateStatusTypeInputFileMissing, data: inputFile} - } - inputPath := orchestrator.toPath(inputFile) - if inputTime.After(oldestOutputFileAndTime.time) { - var version string - var currentVersion string - if buildInfo.IsIncremental() { - if buildInfoRootInfoReader == nil { - buildInfoRootInfoReader = buildInfo.GetBuildInfoRootInfoReader(tspath.GetDirectoryPath(tspath.GetNormalizedAbsolutePath(buildInfoPath, orchestrator.comparePathsOptions.CurrentDirectory)), orchestrator.comparePathsOptions) - } - buildInfoFileInfo, resolvedInputPath := buildInfoRootInfoReader.GetBuildInfoFileInfo(inputPath) - if fileInfo := buildInfoFileInfo.GetFileInfo(); fileInfo != nil && fileInfo.Version() != "" { - version = fileInfo.Version() - if text, ok := orchestrator.host.FS().ReadFile(string(resolvedInputPath)); ok { - currentVersion = incremental.ComputeHash(text, orchestrator.opts.Testing != nil) - if version == currentVersion { - inputTextUnchanged = true - } - } - } - } - - if version == "" || version != currentVersion { - return &upToDateStatus{kind: upToDateStatusTypeInputFileNewer, data: &inputOutputName{inputFile, buildInfoPath}} - } - } - if inputTime.After(newestInputFileAndTime.time) { - newestInputFileAndTime = fileAndTime{inputFile, inputTime} - } - seenRoots.Add(inputPath) - } - - if buildInfoRootInfoReader == nil { - buildInfoRootInfoReader = buildInfo.GetBuildInfoRootInfoReader(tspath.GetDirectoryPath(tspath.GetNormalizedAbsolutePath(buildInfoPath, orchestrator.comparePathsOptions.CurrentDirectory)), orchestrator.comparePathsOptions) - } - for root := range buildInfoRootInfoReader.Roots() { - if !seenRoots.Has(root) { - // File was root file when project was built but its not any more - return &upToDateStatus{kind: upToDateStatusTypeOutOfDateRoots, data: &inputOutputName{string(root), buildInfoPath}} - } - } - - if !t.resolved.CompilerOptions().IsIncremental() { - // Check output file stamps - for outputFile := range t.resolved.GetOutputFileNames() { - outputTime := orchestrator.host.GetMTime(outputFile) - if outputTime.IsZero() { - // Output file missing - return &upToDateStatus{kind: upToDateStatusTypeOutputMissing, data: outputFile} - } - - if outputTime.Before(newestInputFileAndTime.time) { - // Output file is older than input file - return &upToDateStatus{kind: upToDateStatusTypeInputFileNewer, data: &inputOutputName{newestInputFileAndTime.file, outputFile}} - } - - if outputTime.Before(oldestOutputFileAndTime.time) { - oldestOutputFileAndTime = fileAndTime{outputFile, outputTime} - } - } - } - - var refDtsUnchanged bool - for _, upstream := range t.upStream { - if upstream.task.status.kind == upToDateStatusTypeSolution { - // Not dependent on the status or this upstream project - // (eg: expected cycle was detected and hence skipped, or is solution) - continue - } - - // If the upstream project's newest file is older than our oldest output, - // we can't be out of date because of it - // inputTime will not be present if we just built this project or updated timestamps - // - in that case we do want to either build or update timestamps - refInputOutputFileAndTime := upstream.task.status.inputOutputFileAndTime() - if refInputOutputFileAndTime != nil && !refInputOutputFileAndTime.input.time.IsZero() && refInputOutputFileAndTime.input.time.Before(oldestOutputFileAndTime.time) { - continue - } - - // Check if tsbuildinfo path is shared, then we need to rebuild - if t.hasConflictingBuildInfo(orchestrator, upstream.task) { - // We have an output older than an upstream output - we are out of date - return &upToDateStatus{kind: upToDateStatusTypeInputFileNewer, data: &inputOutputName{t.resolved.ProjectReferences()[upstream.refIndex].Path, oldestOutputFileAndTime.file}} - } - - // If the upstream project has only change .d.ts files, and we've built - // *after* those files, then we're "pseudo up to date" and eligible for a fast rebuild - newestDtsChangeTime := upstream.task.getLatestChangedDtsMTime(orchestrator) - if !newestDtsChangeTime.IsZero() && newestDtsChangeTime.Before(oldestOutputFileAndTime.time) { - refDtsUnchanged = true - continue - } - - // We have an output older than an upstream output - we are out of date - return &upToDateStatus{kind: upToDateStatusTypeInputFileNewer, data: &inputOutputName{t.resolved.ProjectReferences()[upstream.refIndex].Path, oldestOutputFileAndTime.file}} - } - - checkInputFileTime := func(inputFile string) *upToDateStatus { - inputTime := orchestrator.host.GetMTime(inputFile) - if inputTime.After(oldestOutputFileAndTime.time) { - // Output file is older than input file - return &upToDateStatus{kind: upToDateStatusTypeInputFileNewer, data: &inputOutputName{inputFile, oldestOutputFileAndTime.file}} - } - return nil - } - - configStatus := checkInputFileTime(t.config) - if configStatus != nil { - return configStatus - } - - for _, extendedConfig := range t.resolved.ExtendedSourceFiles() { - extendedConfigStatus := checkInputFileTime(extendedConfig) - if extendedConfigStatus != nil { - return extendedConfigStatus - } - } - - // !!! sheetal TODO : watch?? - // // Check package file time - // const packageJsonLookups = state.lastCachedPackageJsonLookups.get(resolvedPath); - // const dependentPackageFileStatus = packageJsonLookups && forEachKey( - // packageJsonLookups, - // path => checkConfigFileUpToDateStatus(state, path, oldestOutputFileTime, oldestOutputFileName), - // ); - // if (dependentPackageFileStatus) return dependentPackageFileStatus; - - return &upToDateStatus{ - kind: core.IfElse( - refDtsUnchanged, - upToDateStatusTypeUpToDateWithUpstreamTypes, - core.IfElse(inputTextUnchanged, upToDateStatusTypeUpToDateWithInputFileText, upToDateStatusTypeUpToDate), - ), - data: &inputOutputFileAndTime{newestInputFileAndTime, oldestOutputFileAndTime, buildInfoPath}, - } -} - -func (t *buildTask) reportUpToDateStatus(orchestrator *Orchestrator) { - if !orchestrator.opts.Command.BuildOptions.Verbose.IsTrue() { - return - } - switch t.status.kind { - case upToDateStatusTypeConfigFileNotFound: - t.result.reportStatus(ast.NewCompilerDiagnostic( - diagnostics.Project_0_is_out_of_date_because_config_file_does_not_exist, - orchestrator.relativeFileName(t.config), - )) - case upToDateStatusTypeUpstreamErrors: - upstreamStatus := t.status.upstreamErrors() - t.result.reportStatus(ast.NewCompilerDiagnostic( - core.IfElse( - upstreamStatus.refHasUpstreamErrors, - diagnostics.Project_0_can_t_be_built_because_its_dependency_1_was_not_built, - diagnostics.Project_0_can_t_be_built_because_its_dependency_1_has_errors, - ), - orchestrator.relativeFileName(t.config), - orchestrator.relativeFileName(upstreamStatus.ref), - )) - case upToDateStatusTypeBuildErrors: - t.result.reportStatus(ast.NewCompilerDiagnostic( - diagnostics.Project_0_is_out_of_date_because_it_has_errors, - orchestrator.relativeFileName(t.config), - )) - case upToDateStatusTypeUpToDate: - // This is to ensure skipping verbose log for projects that were built, - // and then some other package changed but this package doesnt need update - if inputOutputFileAndTime := t.status.inputOutputFileAndTime(); inputOutputFileAndTime != nil { - t.result.reportStatus(ast.NewCompilerDiagnostic( - diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_output_2, - orchestrator.relativeFileName(t.config), - orchestrator.relativeFileName(inputOutputFileAndTime.input.file), - orchestrator.relativeFileName(inputOutputFileAndTime.output.file), - )) - } - case upToDateStatusTypeUpToDateWithUpstreamTypes: - t.result.reportStatus(ast.NewCompilerDiagnostic( - diagnostics.Project_0_is_up_to_date_with_d_ts_files_from_its_dependencies, - orchestrator.relativeFileName(t.config), - )) - case upToDateStatusTypeUpToDateWithInputFileText: - t.result.reportStatus(ast.NewCompilerDiagnostic( - diagnostics.Project_0_is_up_to_date_but_needs_to_update_timestamps_of_output_files_that_are_older_than_input_files, - orchestrator.relativeFileName(t.config), - )) - case upToDateStatusTypeInputFileMissing: - t.result.reportStatus(ast.NewCompilerDiagnostic( - diagnostics.Project_0_is_out_of_date_because_input_1_does_not_exist, - orchestrator.relativeFileName(t.config), - orchestrator.relativeFileName(t.status.data.(string)), - )) - case upToDateStatusTypeOutputMissing: - t.result.reportStatus(ast.NewCompilerDiagnostic( - diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, - orchestrator.relativeFileName(t.config), - orchestrator.relativeFileName(t.status.data.(string)), - )) - case upToDateStatusTypeInputFileNewer: - inputOutput := t.status.inputOutputName() - t.result.reportStatus(ast.NewCompilerDiagnostic( - diagnostics.Project_0_is_out_of_date_because_output_1_is_older_than_input_2, - orchestrator.relativeFileName(t.config), - orchestrator.relativeFileName(inputOutput.output), - orchestrator.relativeFileName(inputOutput.input), - )) - case upToDateStatusTypeOutOfDateBuildInfoWithPendingEmit: - t.result.reportStatus(ast.NewCompilerDiagnostic( - diagnostics.Project_0_is_out_of_date_because_buildinfo_file_1_indicates_that_some_of_the_changes_were_not_emitted, - orchestrator.relativeFileName(t.config), - orchestrator.relativeFileName(t.status.data.(string)), - )) - case upToDateStatusTypeOutOfDateBuildInfoWithErrors: - t.result.reportStatus(ast.NewCompilerDiagnostic( - diagnostics.Project_0_is_out_of_date_because_buildinfo_file_1_indicates_that_program_needs_to_report_errors, - orchestrator.relativeFileName(t.config), - orchestrator.relativeFileName(t.status.data.(string)), - )) - case upToDateStatusTypeOutOfDateOptions: - t.result.reportStatus(ast.NewCompilerDiagnostic( - diagnostics.Project_0_is_out_of_date_because_buildinfo_file_1_indicates_there_is_change_in_compilerOptions, - orchestrator.relativeFileName(t.config), - orchestrator.relativeFileName(t.status.data.(string)), - )) - case upToDateStatusTypeOutOfDateRoots: - inputOutput := t.status.inputOutputName() - t.result.reportStatus(ast.NewCompilerDiagnostic( - diagnostics.Project_0_is_out_of_date_because_buildinfo_file_1_indicates_that_file_2_was_root_file_of_compilation_but_not_any_more, - orchestrator.relativeFileName(t.config), - orchestrator.relativeFileName(inputOutput.output), - orchestrator.relativeFileName(inputOutput.input), - )) - case upToDateStatusTypeTsVersionOutputOfDate: - t.result.reportStatus(ast.NewCompilerDiagnostic( - diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, - orchestrator.relativeFileName(t.config), - orchestrator.relativeFileName(t.status.data.(string)), - core.Version(), - )) - case upToDateStatusTypeForceBuild: - t.result.reportStatus(ast.NewCompilerDiagnostic( - diagnostics.Project_0_is_being_forcibly_rebuilt, - orchestrator.relativeFileName(t.config), - )) - case upToDateStatusTypeSolution: - // Does not need to report status - default: - panic(fmt.Sprintf("Unknown up to date status kind: %v", t.status.kind)) - } -} - -func (t *buildTask) canUpdateJsDtsOutputTimestamps() bool { - return !t.resolved.CompilerOptions().NoEmit.IsTrue() && !t.resolved.CompilerOptions().IsIncremental() -} - -func (t *buildTask) updateTimeStamps(orchestrator *Orchestrator, emittedFiles []string, verboseMessage *diagnostics.Message) { - emitted := collections.NewSetFromItems(emittedFiles...) - var verboseMessageReported bool - buildInfoName := t.resolved.GetBuildInfoFileName() - now := orchestrator.opts.Sys.Now() - updateTimeStamp := func(file string) { - if emitted.Has(file) { - return - } - if !verboseMessageReported && orchestrator.opts.Command.BuildOptions.Verbose.IsTrue() { - t.result.reportStatus(ast.NewCompilerDiagnostic(verboseMessage, orchestrator.relativeFileName(t.config))) - verboseMessageReported = true - } - err := orchestrator.host.SetMTime(file, now) - if err == nil { - if file == buildInfoName { - t.buildInfoEntryMu.Lock() - if t.buildInfoEntry != nil { - t.buildInfoEntry.mTime = now - } - t.buildInfoEntryMu.Unlock() - } else if t.storeOutputTimeStamp(orchestrator) { - orchestrator.host.storeMTime(file, now) - } - } - } - - if t.canUpdateJsDtsOutputTimestamps() { - for outputFile := range t.resolved.GetOutputFileNames() { - updateTimeStamp(outputFile) - } - } - updateTimeStamp(t.resolved.GetBuildInfoFileName()) -} - -func (t *buildTask) cleanProject(orchestrator *Orchestrator, path tspath.Path) { - if t.resolved == nil { - t.reportDiagnostic(ast.NewCompilerDiagnostic(diagnostics.File_0_not_found, t.config)) - t.result.exitStatus = tsc.ExitStatusDiagnosticsPresent_OutputsSkipped - return - } - - inputs := collections.NewSetFromItems(core.Map(t.resolved.FileNames(), orchestrator.toPath)...) - for outputFile := range t.resolved.GetOutputFileNames() { - t.cleanProjectOutput(orchestrator, outputFile, inputs) - } - t.cleanProjectOutput(orchestrator, t.resolved.GetBuildInfoFileName(), inputs) -} - -func (t *buildTask) cleanProjectOutput(orchestrator *Orchestrator, outputFile string, inputs *collections.Set[tspath.Path]) { - outputPath := orchestrator.toPath(outputFile) - // If output name is same as input file name, do not delete and ignore the error - if inputs.Has(outputPath) { - return - } - if orchestrator.host.FS().FileExists(outputFile) { - if !orchestrator.opts.Command.BuildOptions.Dry.IsTrue() { - err := orchestrator.host.FS().Remove(outputFile) - if err != nil { - t.reportDiagnostic(ast.NewCompilerDiagnostic(diagnostics.Failed_to_delete_file_0, outputFile)) - } - } else { - t.result.filesToDelete = append(t.result.filesToDelete, outputFile) - } - } -} - -func (t *buildTask) updateWatch(orchestrator *Orchestrator, oldCache *collections.SyncMap[tspath.Path, time.Time]) { - t.configTime = orchestrator.host.loadOrStoreMTime(t.config, oldCache, false) - if t.resolved != nil { - t.extendedConfigTimes = core.Map(t.resolved.ExtendedSourceFiles(), func(p string) time.Time { - return orchestrator.host.loadOrStoreMTime(p, oldCache, false) - }) - t.inputFiles = core.Map(t.resolved.FileNames(), func(p string) time.Time { - return orchestrator.host.loadOrStoreMTime(p, oldCache, false) - }) - if t.canUpdateJsDtsOutputTimestamps() { - for outputFile := range t.resolved.GetOutputFileNames() { - orchestrator.host.storeMTimeFromOldCache(outputFile, oldCache) - } - } - } -} - -func (t *buildTask) resetStatus() { - t.status = nil - t.pending.Store(true) - t.errors = nil -} - -func (t *buildTask) resetConfig(orchestrator *Orchestrator, path tspath.Path) { - t.dirty = true - orchestrator.host.resolvedReferences.delete(path) -} - -func (t *buildTask) hasUpdate(orchestrator *Orchestrator, path tspath.Path) updateKind { - var needsConfigUpdate bool - var needsUpdate bool - if configTime := orchestrator.host.GetMTime(t.config); configTime != t.configTime { - t.resetConfig(orchestrator, path) - needsConfigUpdate = true - } - if t.resolved != nil { - for index, file := range t.resolved.ExtendedSourceFiles() { - if orchestrator.host.GetMTime(file) != t.extendedConfigTimes[index] { - t.resetConfig(orchestrator, path) - needsConfigUpdate = true - } - } - for index, file := range t.resolved.FileNames() { - if orchestrator.host.GetMTime(file) != t.inputFiles[index] { - t.resetStatus() - needsUpdate = true - } - } - if !needsConfigUpdate { - configStart := orchestrator.opts.Sys.Now() - newConfig := t.resolved.ReloadFileNamesOfParsedCommandLine(orchestrator.host.FS()) - configTime := orchestrator.opts.Sys.Now().Sub(configStart) - // Make new channels if needed later - t.reportDone = make(chan struct{}) - t.done = make(chan struct{}) - if !slices.Equal(t.resolved.FileNames(), newConfig.FileNames()) { - orchestrator.host.resolvedReferences.store(path, newConfig) - orchestrator.host.configTimes.Store(path, configTime) - t.resolved = newConfig - t.resetStatus() - needsUpdate = true - } - } - } - return core.IfElse(needsConfigUpdate, updateKindConfig, core.IfElse(needsUpdate, updateKindUpdate, updateKindNone)) -} - -func (t *buildTask) loadOrStoreBuildInfo(orchestrator *Orchestrator, configPath tspath.Path, buildInfoFileName string) (*incremental.BuildInfo, time.Time) { - path := orchestrator.toPath(buildInfoFileName) - t.buildInfoEntryMu.Lock() - defer t.buildInfoEntryMu.Unlock() - if t.buildInfoEntry != nil && t.buildInfoEntry.path == path { - return t.buildInfoEntry.buildInfo, t.buildInfoEntry.mTime - } - t.buildInfoEntry = &buildInfoEntry{ - buildInfo: incremental.NewBuildInfoReader(orchestrator.host).ReadBuildInfo(t.resolved), - path: path, - } - var mTime time.Time - if t.buildInfoEntry.buildInfo != nil { - mTime = orchestrator.host.GetMTime(buildInfoFileName) - } - t.buildInfoEntry.mTime = mTime - return t.buildInfoEntry.buildInfo, mTime -} - -func (t *buildTask) onBuildInfoEmit(orchestrator *Orchestrator, buildInfoFileName string, buildInfo *incremental.BuildInfo, hasChangedDtsFile bool) { - t.buildInfoEntryMu.Lock() - defer t.buildInfoEntryMu.Unlock() - var dtsTime *time.Time - mTime := orchestrator.opts.Sys.Now() - if hasChangedDtsFile { - dtsTime = &mTime - } else if t.buildInfoEntry != nil { - dtsTime = t.buildInfoEntry.dtsTime - } - t.buildInfoEntry = &buildInfoEntry{ - buildInfo: buildInfo, - path: orchestrator.toPath(buildInfoFileName), - mTime: mTime, - dtsTime: dtsTime, - } -} - -func (t *buildTask) hasConflictingBuildInfo(orchestrator *Orchestrator, upstream *buildTask) bool { - if t.buildInfoEntry != nil && upstream.buildInfoEntry != nil { - return t.buildInfoEntry.path == upstream.buildInfoEntry.path - } - return false -} - -func (t *buildTask) getLatestChangedDtsMTime(orchestrator *Orchestrator) time.Time { - t.buildInfoEntryMu.Lock() - defer t.buildInfoEntryMu.Unlock() - if t.buildInfoEntry.dtsTime != nil { - return *t.buildInfoEntry.dtsTime - } - dtsTime := orchestrator.host.GetMTime( - tspath.GetNormalizedAbsolutePath( - t.buildInfoEntry.buildInfo.LatestChangedDtsFile, - tspath.GetDirectoryPath(string(t.buildInfoEntry.path)), - ), - ) - t.buildInfoEntry.dtsTime = &dtsTime - return dtsTime -} - -func (t *buildTask) storeOutputTimeStamp(orchestrator *Orchestrator) bool { - return orchestrator.opts.Command.CompilerOptions.Watch.IsTrue() && !t.resolved.CompilerOptions().IsIncremental() -} - -func (t *buildTask) writeFile(orchestrator *Orchestrator, fileName string, text string, writeByteOrderMark bool, data *compiler.WriteFileData) error { - err := orchestrator.host.FS().WriteFile(fileName, text, writeByteOrderMark) - if err == nil { - if data != nil && data.BuildInfo != nil { - t.onBuildInfoEmit(orchestrator, fileName, data.BuildInfo.(*incremental.BuildInfo), t.result.program.HasChangedDtsFile()) - } else if t.storeOutputTimeStamp(orchestrator) { - // Store time stamps - orchestrator.host.storeMTime(fileName, orchestrator.opts.Sys.Now()) - } - } - return err -} diff --git a/kitcom/internal/tsgo/execute/build/compilerHost.go b/kitcom/internal/tsgo/execute/build/compilerHost.go deleted file mode 100644 index 718b540..0000000 --- a/kitcom/internal/tsgo/execute/build/compilerHost.go +++ /dev/null @@ -1,40 +0,0 @@ -package build - -import ( - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/compiler" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tsoptions" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/vfs" -) - -type compilerHost struct { - host *host - trace func(msg string) -} - -var _ compiler.CompilerHost = (*compilerHost)(nil) - -func (h *compilerHost) FS() vfs.FS { - return h.host.FS() -} - -func (h *compilerHost) DefaultLibraryPath() string { - return h.host.DefaultLibraryPath() -} - -func (h *compilerHost) GetCurrentDirectory() string { - return h.host.GetCurrentDirectory() -} - -func (h *compilerHost) Trace(msg string) { - h.trace(msg) -} - -func (h *compilerHost) GetSourceFile(opts ast.SourceFileParseOptions) *ast.SourceFile { - return h.host.GetSourceFile(opts) -} - -func (h *compilerHost) GetResolvedProjectReference(fileName string, path tspath.Path) *tsoptions.ParsedCommandLine { - return h.host.GetResolvedProjectReference(fileName, path) -} diff --git a/kitcom/internal/tsgo/execute/build/graph_test.go b/kitcom/internal/tsgo/execute/build/graph_test.go deleted file mode 100644 index 448e647..0000000 --- a/kitcom/internal/tsgo/execute/build/graph_test.go +++ /dev/null @@ -1,153 +0,0 @@ -package build_test - -import ( - "fmt" - "slices" - "strings" - "testing" - - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/core" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/execute/build" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/execute/tsctests" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tsoptions" - "gotest.tools/v3/assert" -) - -func TestBuildOrderGenerator(t *testing.T) { - t.Parallel() - testCases := []*buildOrderTestCase{ - {"specify two roots", []string{"A", "G"}, []string{"D", "E", "C", "B", "A", "G"}, false}, - {"multiple parts of the same graph in various orders", []string{"A"}, []string{"D", "E", "C", "B", "A"}, false}, - {"multiple parts of the same graph in various orders", []string{"A", "C", "D"}, []string{"D", "E", "C", "B", "A"}, false}, - {"multiple parts of the same graph in various orders", []string{"D", "C", "A"}, []string{"D", "E", "C", "B", "A"}, false}, - {"other orderings", []string{"F"}, []string{"E", "F"}, false}, - {"other orderings", []string{"E"}, []string{"E"}, false}, - {"other orderings", []string{"F", "C", "A"}, []string{"E", "F", "D", "C", "B", "A"}, false}, - {"returns circular order", []string{"H"}, []string{"E", "J", "I", "H"}, true}, - {"returns circular order", []string{"A", "H"}, []string{"D", "E", "C", "B", "A", "J", "I", "H"}, true}, - } - for _, testcase := range testCases { - testcase.run(t) - } -} - -type buildOrderTestCase struct { - name string - projects []string - expected []string - circular bool -} - -func (b *buildOrderTestCase) configName(project string) string { - return fmt.Sprintf("/home/src/workspaces/project/%s/tsconfig.json", project) -} - -func (b *buildOrderTestCase) projectName(config string) string { - str := strings.TrimPrefix(config, "/home/src/workspaces/project/") - str = strings.TrimSuffix(str, "/tsconfig.json") - return str -} - -func (b *buildOrderTestCase) run(t *testing.T) { - t.Helper() - t.Run(b.name+" - "+strings.Join(b.projects, ","), func(t *testing.T) { - t.Parallel() - files := make(map[string]any) - deps := map[string][]string{ - "A": {"B", "C"}, - "B": {"C", "D"}, - "C": {"D", "E"}, - "F": {"E"}, - "H": {"I"}, - "I": {"J"}, - "J": {"H", "E"}, - } - reverseDeps := map[string][]string{} - for project, deps := range deps { - for _, dep := range deps { - reverseDeps[dep] = append(reverseDeps[dep], project) - } - } - verifyDeps := func(orchestrator *build.Orchestrator, buildOrder []string, hasDownStream bool) { - for index, project := range buildOrder { - upstream := core.Map(orchestrator.Upstream(b.configName(project)), b.projectName) - expectedUpstream := deps[project] - assert.Assert(t, len(upstream) <= len(expectedUpstream), fmt.Sprintf("Expected upstream for %s to be at most %d, got %d", project, len(expectedUpstream), len(upstream))) - for _, expected := range expectedUpstream { - if slices.Contains(buildOrder[:index], expected) { - assert.Assert(t, slices.Contains(upstream, expected), fmt.Sprintf("Expected upstream for %s to contain %s", project, expected)) - } else { - assert.Assert(t, !slices.Contains(upstream, expected), fmt.Sprintf("Expected upstream for %s to not contain %s", project, expected)) - } - } - - downstream := core.Map(orchestrator.Downstream(b.configName(project)), b.projectName) - expectedDownstream := core.IfElse(hasDownStream, reverseDeps[project], nil) - assert.Assert(t, len(downstream) <= len(expectedDownstream), fmt.Sprintf("Expected downstream for %s to be at most %d, got %d", project, len(expectedDownstream), len(downstream))) - for _, expected := range expectedDownstream { - if slices.Contains(buildOrder[index+1:], expected) { - assert.Assert(t, slices.Contains(downstream, expected), fmt.Sprintf("Expected downstream for %s to contain %s", project, expected)) - } else { - assert.Assert(t, !slices.Contains(downstream, expected), fmt.Sprintf("Expected downstream for %s to not contain %s", project, expected)) - } - } - } - } - for _, project := range []string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J"} { - files[fmt.Sprintf("/home/src/workspaces/project/%s/%s.ts", project, project)] = "export {}" - referencesStr := "" - if deps, ok := deps[project]; ok { - referencesStr = fmt.Sprintf(`, "references": [%s]`, strings.Join(core.Map(deps, func(dep string) string { - return fmt.Sprintf(`{ "path": "../%s" }`, dep) - }), ",")) - } - files[b.configName(project)] = fmt.Sprintf(`{ - "compilerOptions": { "composite": true }, - "files": ["./%s.ts"], - %s - }`, project, referencesStr) - } - - sys := tsctests.NewTscSystem(files, true, "/home/src/workspaces/project") - args := append([]string{"--build", "--dry"}, b.projects...) - buildCommand := tsoptions.ParseBuildCommandLine(args, sys) - orchestrator := build.NewOrchestrator(build.Options{ - Sys: sys, - Command: buildCommand, - }) - orchestrator.GenerateGraph(nil) - buildOrder := core.Map(orchestrator.Order(), b.projectName) - assert.DeepEqual(t, buildOrder, b.expected) - verifyDeps(orchestrator, buildOrder, false) - - if !b.circular { - for project, projectDeps := range deps { - child := b.configName(project) - childIndex := slices.Index(buildOrder, child) - if childIndex == -1 { - continue - } - for _, dep := range projectDeps { - parent := b.configName(dep) - parentIndex := slices.Index(buildOrder, parent) - - assert.Assert(t, childIndex > parentIndex, fmt.Sprintf("Expecting child %s to be built after parent %s", project, dep)) - } - } - } - - orchestrator.GenerateGraphReusingOldTasks() - buildOrder2 := core.Map(orchestrator.Order(), b.projectName) - assert.DeepEqual(t, buildOrder2, b.expected) - - argsWatch := append([]string{"--build", "--watch"}, b.projects...) - buildCommandWatch := tsoptions.ParseBuildCommandLine(argsWatch, sys) - orchestrator = build.NewOrchestrator(build.Options{ - Sys: sys, - Command: buildCommandWatch, - }) - orchestrator.GenerateGraph(nil) - buildOrder3 := core.Map(orchestrator.Order(), b.projectName) - verifyDeps(orchestrator, buildOrder3, true) - }) -} diff --git a/kitcom/internal/tsgo/execute/build/host.go b/kitcom/internal/tsgo/execute/build/host.go deleted file mode 100644 index 7cb6f0e..0000000 --- a/kitcom/internal/tsgo/execute/build/host.go +++ /dev/null @@ -1,113 +0,0 @@ -package build - -import ( - "time" - - "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/execute/incremental" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/execute/tsc" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tsoptions" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/vfs" -) - -type host struct { - orchestrator *Orchestrator - host compiler.CompilerHost - - // Caches that last only for build cycle and then cleared out - extendedConfigCache tsc.ExtendedConfigCache - sourceFiles parseCache[ast.SourceFileParseOptions, *ast.SourceFile] - configTimes collections.SyncMap[tspath.Path, time.Duration] - - // caches that stay as long as they are needed - resolvedReferences parseCache[tspath.Path, *tsoptions.ParsedCommandLine] - mTimes *collections.SyncMap[tspath.Path, time.Time] -} - -var ( - _ compiler.CompilerHost = (*host)(nil) - _ incremental.BuildInfoReader = (*host)(nil) - _ incremental.Host = (*host)(nil) -) - -func (h *host) FS() vfs.FS { - return h.host.FS() -} - -func (h *host) DefaultLibraryPath() string { - return h.host.DefaultLibraryPath() -} - -func (h *host) GetCurrentDirectory() string { - return h.host.GetCurrentDirectory() -} - -func (h *host) Trace(msg string) { - panic("build.Orchestrator.host does not support tracing, use a different host for tracing") -} - -func (h *host) GetSourceFile(opts ast.SourceFileParseOptions) *ast.SourceFile { - // Cache dts and json files as they will be reused - return h.sourceFiles.loadOrStoreNewIf(opts, h.host.GetSourceFile, func(value *ast.SourceFile) bool { - return value != nil && (tspath.IsDeclarationFileName(opts.FileName) || tspath.FileExtensionIs(opts.FileName, tspath.ExtensionJson)) - }) -} - -func (h *host) GetResolvedProjectReference(fileName string, path tspath.Path) *tsoptions.ParsedCommandLine { - return h.resolvedReferences.loadOrStoreNew(path, func(path tspath.Path) *tsoptions.ParsedCommandLine { - configStart := h.orchestrator.opts.Sys.Now() - commandLine, _ := tsoptions.GetParsedCommandLineOfConfigFilePath(fileName, path, h.orchestrator.opts.Command.CompilerOptions, h, &h.extendedConfigCache) - configTime := h.orchestrator.opts.Sys.Now().Sub(configStart) - h.configTimes.Store(path, configTime) - return commandLine - }) -} - -func (h *host) ReadBuildInfo(config *tsoptions.ParsedCommandLine) *incremental.BuildInfo { - configPath := h.orchestrator.toPath(config.ConfigName()) - task := h.orchestrator.getTask(configPath) - buildInfo, _ := task.loadOrStoreBuildInfo(h.orchestrator, h.orchestrator.toPath(config.ConfigName()), config.GetBuildInfoFileName()) - return buildInfo -} - -func (h *host) GetMTime(file string) time.Time { - return h.loadOrStoreMTime(file, nil, true) -} - -func (h *host) SetMTime(file string, mTime time.Time) error { - return h.FS().Chtimes(file, time.Time{}, mTime) -} - -func (h *host) loadOrStoreMTime(file string, oldCache *collections.SyncMap[tspath.Path, time.Time], store bool) time.Time { - path := h.orchestrator.toPath(file) - if existing, loaded := h.mTimes.Load(path); loaded { - return existing - } - var found bool - var mTime time.Time - if oldCache != nil { - mTime, found = oldCache.Load(path) - } - if !found { - mTime = incremental.GetMTime(h.host, file) - } - if store { - mTime, _ = h.mTimes.LoadOrStore(path, mTime) - } - return mTime -} - -func (h *host) storeMTime(file string, mTime time.Time) { - path := h.orchestrator.toPath(file) - h.mTimes.Store(path, mTime) -} - -func (h *host) storeMTimeFromOldCache(file string, oldCache *collections.SyncMap[tspath.Path, time.Time]) { - path := h.orchestrator.toPath(file) - if mTime, found := oldCache.Load(path); found { - h.mTimes.Store(path, mTime) - } -} diff --git a/kitcom/internal/tsgo/execute/build/orchestrator.go b/kitcom/internal/tsgo/execute/build/orchestrator.go deleted file mode 100644 index ee7cc7a..0000000 --- a/kitcom/internal/tsgo/execute/build/orchestrator.go +++ /dev/null @@ -1,400 +0,0 @@ -package build - -import ( - "io" - "strings" - "sync/atomic" - "time" - - "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/diagnostics" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/execute/tsc" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tsoptions" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/vfs/cachedvfs" -) - -type Options struct { - Sys tsc.System - Command *tsoptions.ParsedBuildCommandLine - Testing tsc.CommandLineTesting -} - -type orchestratorResult struct { - result tsc.CommandLineResult - errors []*ast.Diagnostic - statistics tsc.Statistics - filesToDelete []string -} - -func (b *orchestratorResult) report(o *Orchestrator) { - if o.opts.Command.CompilerOptions.Watch.IsTrue() { - o.watchStatusReporter(ast.NewCompilerDiagnostic(core.IfElse(len(b.errors) == 1, diagnostics.Found_1_error_Watching_for_file_changes, diagnostics.Found_0_errors_Watching_for_file_changes), len(b.errors))) - } else { - o.errorSummaryReporter(b.errors) - } - if b.filesToDelete != nil { - o.createBuilderStatusReporter(nil)( - ast.NewCompilerDiagnostic( - diagnostics.A_non_dry_build_would_delete_the_following_files_Colon_0, - strings.Join(core.Map(b.filesToDelete, func(f string) string { - return "\r\n * " + f - }), ""), - )) - } - if !o.opts.Command.CompilerOptions.Diagnostics.IsTrue() && !o.opts.Command.CompilerOptions.ExtendedDiagnostics.IsTrue() { - return - } - b.statistics.SetTotalTime(o.opts.Sys.SinceStart()) - b.statistics.Report(o.opts.Sys.Writer(), o.opts.Testing) -} - -type Orchestrator struct { - opts Options - comparePathsOptions tspath.ComparePathsOptions - host *host - - // order generation result - tasks *collections.SyncMap[tspath.Path, *buildTask] - order []string - errors []*ast.Diagnostic - - errorSummaryReporter tsc.DiagnosticsReporter - watchStatusReporter tsc.DiagnosticReporter -} - -var _ tsc.Watcher = (*Orchestrator)(nil) - -func (o *Orchestrator) relativeFileName(fileName string) string { - return tspath.ConvertToRelativePath(fileName, o.comparePathsOptions) -} - -func (o *Orchestrator) toPath(fileName string) tspath.Path { - return tspath.ToPath(fileName, o.comparePathsOptions.CurrentDirectory, o.comparePathsOptions.UseCaseSensitiveFileNames) -} - -func (o *Orchestrator) Order() []string { - return o.order -} - -func (o *Orchestrator) Upstream(configName string) []string { - path := o.toPath(configName) - task := o.getTask(path) - return core.Map(task.upStream, func(t *upstreamTask) string { - return t.task.config - }) -} - -func (o *Orchestrator) Downstream(configName string) []string { - path := o.toPath(configName) - task := o.getTask(path) - return core.Map(task.downStream, func(t *buildTask) string { - return t.config - }) -} - -func (o *Orchestrator) getTask(path tspath.Path) *buildTask { - task, ok := o.tasks.Load(path) - if !ok { - panic("No build task found for " + path) - } - return task -} - -func (o *Orchestrator) createBuildTasks(oldTasks *collections.SyncMap[tspath.Path, *buildTask], configs []string, wg core.WorkGroup) { - for _, config := range configs { - wg.Queue(func() { - path := o.toPath(config) - var task *buildTask - var buildInfo *buildInfoEntry - if oldTasks != nil { - if existing, ok := oldTasks.Load(path); ok { - if !existing.dirty { - // Reuse existing task if config is same - task = existing - } else { - buildInfo = existing.buildInfoEntry - } - } - } - if task == nil { - task = &buildTask{config: config, isInitialCycle: oldTasks == nil} - task.pending.Store(true) - task.buildInfoEntry = buildInfo - } - if _, loaded := o.tasks.LoadOrStore(path, task); loaded { - return - } - task.resolved = o.host.GetResolvedProjectReference(config, path) - task.upStream = nil - if task.resolved != nil { - o.createBuildTasks(oldTasks, task.resolved.ResolvedProjectReferencePaths(), wg) - } - }) - } -} - -func (o *Orchestrator) setupBuildTask( - configName string, - downStream *buildTask, - inCircularContext bool, - completed *collections.Set[tspath.Path], - analyzing *collections.Set[tspath.Path], - circularityStack []string, -) *buildTask { - path := o.toPath(configName) - task := o.getTask(path) - if !completed.Has(path) { - if analyzing.Has(path) { - if !inCircularContext { - o.errors = append(o.errors, ast.NewCompilerDiagnostic( - diagnostics.Project_references_may_not_form_a_circular_graph_Cycle_detected_Colon_0, - strings.Join(circularityStack, "\n"), - )) - } - return nil - } - analyzing.Add(path) - circularityStack = append(circularityStack, configName) - if task.resolved != nil { - for index, subReference := range task.resolved.ResolvedProjectReferencePaths() { - upstream := o.setupBuildTask(subReference, task, inCircularContext || task.resolved.ProjectReferences()[index].Circular, completed, analyzing, circularityStack) - if upstream != nil { - task.upStream = append(task.upStream, &upstreamTask{task: upstream, refIndex: index}) - } - } - } - circularityStack = circularityStack[:len(circularityStack)-1] - completed.Add(path) - task.reportDone = make(chan struct{}) - prev := core.LastOrNil(o.order) - if prev != "" { - task.prevReporter = o.getTask(o.toPath(prev)) - } - task.done = make(chan struct{}) - o.order = append(o.order, configName) - } - if o.opts.Command.CompilerOptions.Watch.IsTrue() && downStream != nil { - task.downStream = append(task.downStream, downStream) - } - return task -} - -func (o *Orchestrator) GenerateGraphReusingOldTasks() { - tasks := o.tasks - o.tasks = &collections.SyncMap[tspath.Path, *buildTask]{} - o.order = nil - o.errors = nil - o.GenerateGraph(tasks) -} - -func (o *Orchestrator) GenerateGraph(oldTasks *collections.SyncMap[tspath.Path, *buildTask]) { - projects := o.opts.Command.ResolvedProjectPaths() - // Parse all config files in parallel - wg := core.NewWorkGroup(o.opts.Command.CompilerOptions.SingleThreaded.IsTrue()) - o.createBuildTasks(oldTasks, projects, wg) - wg.RunAndWait() - - // Generate the graph - completed := collections.Set[tspath.Path]{} - analyzing := collections.Set[tspath.Path]{} - circularityStack := []string{} - for _, project := range projects { - o.setupBuildTask(project, nil, false, &completed, &analyzing, circularityStack) - } -} - -func (o *Orchestrator) Start() tsc.CommandLineResult { - if o.opts.Command.CompilerOptions.Watch.IsTrue() { - o.watchStatusReporter(ast.NewCompilerDiagnostic(diagnostics.Starting_compilation_in_watch_mode)) - } - o.GenerateGraph(nil) - result := o.buildOrClean() - if o.opts.Command.CompilerOptions.Watch.IsTrue() { - o.Watch() - result.Watcher = o - } - return result -} - -func (o *Orchestrator) Watch() { - o.updateWatch() - o.resetCaches() - - // Start watching for file changes - if o.opts.Testing == nil { - watchInterval := o.opts.Command.WatchOptions.WatchInterval() - for { - // Testing mode: run a single cycle and exit - time.Sleep(watchInterval) - o.DoCycle() - } - } -} - -func (o *Orchestrator) updateWatch() { - oldCache := o.host.mTimes - o.host.mTimes = &collections.SyncMap[tspath.Path, time.Time]{} - wg := core.NewWorkGroup(o.opts.Command.CompilerOptions.SingleThreaded.IsTrue()) - o.tasks.Range(func(path tspath.Path, task *buildTask) bool { - wg.Queue(func() { - task.updateWatch(o, oldCache) - }) - return true - }) - wg.RunAndWait() -} - -func (o *Orchestrator) resetCaches() { - // Clean out all the caches - cachesVfs := o.host.host.FS().(*cachedvfs.FS) - cachesVfs.ClearCache() - o.host.extendedConfigCache = tsc.ExtendedConfigCache{} - o.host.sourceFiles.reset() - o.host.configTimes = collections.SyncMap[tspath.Path, time.Duration]{} -} - -func (o *Orchestrator) DoCycle() { - var needsConfigUpdate atomic.Bool - var needsUpdate atomic.Bool - mTimes := o.host.mTimes.Clone() - wg := core.NewWorkGroup(o.opts.Command.CompilerOptions.SingleThreaded.IsTrue()) - o.tasks.Range(func(path tspath.Path, task *buildTask) bool { - wg.Queue(func() { - if updateKind := task.hasUpdate(o, path); updateKind != updateKindNone { - needsUpdate.Store(true) - if updateKind == updateKindConfig { - needsConfigUpdate.Store(true) - } - } - }) - // Watch for file changes - return true - }) - wg.RunAndWait() - - if !needsUpdate.Load() { - o.host.mTimes = mTimes - o.resetCaches() - return - } - - o.watchStatusReporter(ast.NewCompilerDiagnostic(diagnostics.File_change_detected_Starting_incremental_compilation)) - if needsConfigUpdate.Load() { - // Generate new tasks - o.GenerateGraphReusingOldTasks() - } - - o.buildOrClean() - o.updateWatch() - o.resetCaches() -} - -func (o *Orchestrator) buildOrClean() tsc.CommandLineResult { - if !o.opts.Command.BuildOptions.Clean.IsTrue() && o.opts.Command.BuildOptions.Verbose.IsTrue() { - o.createBuilderStatusReporter(nil)(ast.NewCompilerDiagnostic( - diagnostics.Projects_in_this_build_Colon_0, - strings.Join(core.Map(o.Order(), func(p string) string { - return "\r\n * " + o.relativeFileName(p) - }), ""), - )) - } - var buildResult orchestratorResult - if len(o.errors) == 0 { - buildResult.statistics.Projects = len(o.Order()) - if o.opts.Command.CompilerOptions.SingleThreaded.IsTrue() { - o.singleThreadedBuildOrClean(&buildResult) - } else { - o.multiThreadedBuildOrClean(&buildResult) - } - } else { - // Circularity errors prevent any project from being built - buildResult.result.Status = tsc.ExitStatusProjectReferenceCycle_OutputsSkipped - reportDiagnostic := o.createDiagnosticReporter(nil) - for _, err := range o.errors { - reportDiagnostic(err) - } - buildResult.errors = o.errors - } - buildResult.report(o) - return buildResult.result -} - -func (o *Orchestrator) singleThreadedBuildOrClean(buildResult *orchestratorResult) { - // Go in the order since only one project can be built at a time so that random order isnt picked by work group creating deadlock - for _, config := range o.Order() { - path := o.toPath(config) - task := o.getTask(path) - o.buildOrCleanProject(task, path, buildResult) - } -} - -func (o *Orchestrator) multiThreadedBuildOrClean(buildResult *orchestratorResult) { - // Spin off the threads with waiting on upstream to build before actual project build - wg := core.NewWorkGroup(false) - o.tasks.Range(func(path tspath.Path, task *buildTask) bool { - wg.Queue(func() { - o.buildOrCleanProject(task, path, buildResult) - }) - return true - }) - wg.RunAndWait() -} - -func (o *Orchestrator) buildOrCleanProject(task *buildTask, path tspath.Path, buildResult *orchestratorResult) { - task.result = &taskResult{} - task.result.reportStatus = o.createBuilderStatusReporter(task) - task.result.diagnosticReporter = o.createDiagnosticReporter(task) - if !o.opts.Command.BuildOptions.Clean.IsTrue() { - task.buildProject(o, path) - } else { - task.cleanProject(o, path) - } - task.report(o, path, buildResult) -} - -func (o *Orchestrator) getWriter(task *buildTask) io.Writer { - if task == nil { - return o.opts.Sys.Writer() - } - return &task.result.builder -} - -func (o *Orchestrator) createBuilderStatusReporter(task *buildTask) tsc.DiagnosticReporter { - return tsc.CreateBuilderStatusReporter(o.opts.Sys, o.getWriter(task), o.opts.Command.CompilerOptions, o.opts.Testing) -} - -func (o *Orchestrator) createDiagnosticReporter(task *buildTask) tsc.DiagnosticReporter { - return tsc.CreateDiagnosticReporter(o.opts.Sys, o.getWriter(task), o.opts.Command.CompilerOptions) -} - -func NewOrchestrator(opts Options) *Orchestrator { - orchestrator := &Orchestrator{ - opts: opts, - comparePathsOptions: tspath.ComparePathsOptions{ - CurrentDirectory: opts.Sys.GetCurrentDirectory(), - UseCaseSensitiveFileNames: opts.Sys.FS().UseCaseSensitiveFileNames(), - }, - tasks: &collections.SyncMap[tspath.Path, *buildTask]{}, - } - orchestrator.host = &host{ - orchestrator: orchestrator, - host: compiler.NewCachedFSCompilerHost( - orchestrator.opts.Sys.GetCurrentDirectory(), - orchestrator.opts.Sys.FS(), - orchestrator.opts.Sys.DefaultLibraryPath(), - nil, - nil, - ), - mTimes: &collections.SyncMap[tspath.Path, time.Time]{}, - } - if opts.Command.CompilerOptions.Watch.IsTrue() { - orchestrator.watchStatusReporter = tsc.CreateWatchStatusReporter(opts.Sys, opts.Command.CompilerOptions, opts.Testing) - } else { - orchestrator.errorSummaryReporter = tsc.CreateReportErrorSummary(opts.Sys, opts.Command.CompilerOptions) - } - return orchestrator -} diff --git a/kitcom/internal/tsgo/execute/build/parseCache.go b/kitcom/internal/tsgo/execute/build/parseCache.go deleted file mode 100644 index f3b3e70..0000000 --- a/kitcom/internal/tsgo/execute/build/parseCache.go +++ /dev/null @@ -1,48 +0,0 @@ -package build - -import ( - "sync" - - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/collections" -) - -type parseCacheEntry[V any] struct { - value V - mu sync.Mutex -} - -type parseCache[K comparable, V any] struct { - entries collections.SyncMap[K, *parseCacheEntry[V]] -} - -func (c *parseCache[K, V]) loadOrStoreNew(key K, parse func(K) V) V { - return c.loadOrStoreNewIf(key, parse, func(value V) bool { return true }) -} - -func (c *parseCache[K, V]) loadOrStoreNewIf(key K, parse func(K) V, canCacheValue func(V) bool) V { - newEntry := &parseCacheEntry[V]{} - newEntry.mu.Lock() - defer newEntry.mu.Unlock() - if entry, loaded := c.entries.LoadOrStore(key, newEntry); loaded { - entry.mu.Lock() - defer entry.mu.Unlock() - if canCacheValue(entry.value) { - return entry.value - } - newEntry = entry - } - newEntry.value = parse(key) - return newEntry.value -} - -func (c *parseCache[K, V]) store(key K, value V) { - c.entries.Store(key, &parseCacheEntry[V]{value: value}) -} - -func (c *parseCache[K, V]) delete(key K) { - c.entries.Delete(key) -} - -func (c *parseCache[K, V]) reset() { - c.entries = collections.SyncMap[K, *parseCacheEntry[V]]{} -} diff --git a/kitcom/internal/tsgo/execute/build/uptodatestatus.go b/kitcom/internal/tsgo/execute/build/uptodatestatus.go deleted file mode 100644 index ca34778..0000000 --- a/kitcom/internal/tsgo/execute/build/uptodatestatus.go +++ /dev/null @@ -1,133 +0,0 @@ -package build - -import "time" - -type upToDateStatusType uint16 - -const ( - // Errors: - - // config file was not found - upToDateStatusTypeConfigFileNotFound upToDateStatusType = iota - // found errors during build - upToDateStatusTypeBuildErrors - // did not build because upstream project has errors - and we have option to stop build on upstream errors - upToDateStatusTypeUpstreamErrors - - // Its all good, no work to do - upToDateStatusTypeUpToDate - - // Pseudo-builds - touch timestamps, no actual build: - - // The project appears out of date because its upstream inputs are newer than its outputs, - // but all of its outputs are actually newer than the previous identical outputs of its (.d.ts) inputs. - // This means we can Pseudo-build (just touch timestamps), as if we had actually built this project. - upToDateStatusTypeUpToDateWithUpstreamTypes - // The project appears up to date and even though input file changed, its text didnt so just need to update timestamps - upToDateStatusTypeUpToDateWithInputFileText - - // Needs build: - - // input file is missing - upToDateStatusTypeInputFileMissing - // output file is missing - upToDateStatusTypeOutputMissing - // input file is newer than output file - upToDateStatusTypeInputFileNewer - // build info is out of date as we need to emit some files - upToDateStatusTypeOutOfDateBuildInfoWithPendingEmit - // build info indicates that project has errors and they need to be reported - upToDateStatusTypeOutOfDateBuildInfoWithErrors - // build info options indicate there is work to do based on changes in options - upToDateStatusTypeOutOfDateOptions - // file was root when built but not any more - upToDateStatusTypeOutOfDateRoots - // buildInfo.version mismatch with current ts version - upToDateStatusTypeTsVersionOutputOfDate - // build because --force was specified - upToDateStatusTypeForceBuild - - // solution file - upToDateStatusTypeSolution -) - -type inputOutputName struct { - input string - output string -} - -type fileAndTime struct { - file string - time time.Time -} - -type inputOutputFileAndTime struct { - input fileAndTime - output fileAndTime - buildInfo string -} - -type upstreamErrors struct { - ref string - refHasUpstreamErrors bool -} - -type upToDateStatus struct { - kind upToDateStatusType - data any -} - -func (s *upToDateStatus) isError() bool { - switch s.kind { - case upToDateStatusTypeConfigFileNotFound, - upToDateStatusTypeBuildErrors, - upToDateStatusTypeUpstreamErrors: - return true - default: - return false - } -} - -func (s *upToDateStatus) isPseudoBuild() bool { - switch s.kind { - case upToDateStatusTypeUpToDateWithUpstreamTypes, - upToDateStatusTypeUpToDateWithInputFileText: - return true - default: - return false - } -} - -func (s *upToDateStatus) inputOutputFileAndTime() *inputOutputFileAndTime { - data, ok := s.data.(*inputOutputFileAndTime) - if !ok { - return nil - } - return data -} - -func (s *upToDateStatus) inputOutputName() *inputOutputName { - data, ok := s.data.(*inputOutputName) - if !ok { - return nil - } - return data -} - -func (s *upToDateStatus) oldestOutputFileName() string { - if !s.isPseudoBuild() && s.kind != upToDateStatusTypeUpToDate { - panic("only valid for up to date status of pseudo-build or up to date") - } - - if inputOutputFileAndTime := s.inputOutputFileAndTime(); inputOutputFileAndTime != nil { - return inputOutputFileAndTime.output.file - } - if inputOutputName := s.inputOutputName(); inputOutputName != nil { - return inputOutputName.output - } - return s.data.(string) -} - -func (s *upToDateStatus) upstreamErrors() *upstreamErrors { - return s.data.(*upstreamErrors) -} diff --git a/kitcom/internal/tsgo/execute/incremental/affectedfileshandler.go b/kitcom/internal/tsgo/execute/incremental/affectedfileshandler.go deleted file mode 100644 index 000e886..0000000 --- a/kitcom/internal/tsgo/execute/incremental/affectedfileshandler.go +++ /dev/null @@ -1,384 +0,0 @@ -package incremental - -import ( - "context" - "maps" - "slices" - "sync" - "sync/atomic" - - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/checker" - "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/tspath" -) - -type dtsMayChange map[tspath.Path]FileEmitKind - -func (c dtsMayChange) addFileToAffectedFilesPendingEmit(filePath tspath.Path, emitKind FileEmitKind) { - c[filePath] = emitKind -} - -type updatedSignature struct { - mu sync.Mutex - signature string - kind SignatureUpdateKind -} - -type affectedFilesHandler struct { - ctx context.Context - program *Program - hasAllFilesExcludingDefaultLibraryFile atomic.Bool - updatedSignatures collections.SyncMap[tspath.Path, *updatedSignature] - dtsMayChange []dtsMayChange - filesToRemoveDiagnostics collections.SyncSet[tspath.Path] - cleanedDiagnosticsOfLibFiles sync.Once - seenFileAndExportsOfFile collections.SyncMap[tspath.Path, bool] -} - -func (h *affectedFilesHandler) getDtsMayChange(affectedFilePath tspath.Path, affectedFileEmitKind FileEmitKind) dtsMayChange { - result := dtsMayChange(map[tspath.Path]FileEmitKind{affectedFilePath: affectedFileEmitKind}) - h.dtsMayChange = append(h.dtsMayChange, result) - return result -} - -func (h *affectedFilesHandler) isChangedSignature(path tspath.Path) bool { - newSignature, _ := h.updatedSignatures.Load(path) - // This method is called after updating signatures of that path, so signature is present in updatedSignatures - // And is already calculated, so no need to lock and unlock mutex on the entry - oldInfo, _ := h.program.snapshot.fileInfos.Load(path) - return newSignature.signature != oldInfo.signature -} - -func (h *affectedFilesHandler) removeSemanticDiagnosticsOf(path tspath.Path) { - h.filesToRemoveDiagnostics.Add(path) -} - -func (h *affectedFilesHandler) removeDiagnosticsOfLibraryFiles() { - h.cleanedDiagnosticsOfLibFiles.Do(func() { - for _, file := range h.program.GetSourceFiles() { - if h.program.program.IsSourceFileDefaultLibrary(file.Path()) && !checker.SkipTypeChecking(file, h.program.snapshot.options, h.program.program, true) { - h.removeSemanticDiagnosticsOf(file.Path()) - } - } - }) -} - -func (h *affectedFilesHandler) computeDtsSignature(file *ast.SourceFile) string { - var signature string - h.program.program.Emit(h.ctx, compiler.EmitOptions{ - TargetSourceFile: file, - EmitOnly: compiler.EmitOnlyForcedDts, - WriteFile: func(fileName string, text string, writeByteOrderMark bool, data *compiler.WriteFileData) error { - if !tspath.IsDeclarationFileName(fileName) { - panic("File extension for signature expected to be dts, got : " + fileName) - } - signature = h.program.snapshot.computeSignatureWithDiagnostics(file, text, data) - return nil - }, - }) - return signature -} - -func (h *affectedFilesHandler) updateShapeSignature(file *ast.SourceFile, useFileVersionAsSignature bool) bool { - update := &updatedSignature{} - update.mu.Lock() - defer update.mu.Unlock() - // If we have cached the result for this file, that means hence forth we should assume file shape is uptodate - if existing, ok := h.updatedSignatures.LoadOrStore(file.Path(), update); ok { - // Ensure calculations for existing ones are complete before using the value - existing.mu.Lock() - defer existing.mu.Unlock() - return false - } - - info, _ := h.program.snapshot.fileInfos.Load(file.Path()) - prevSignature := info.signature - if !file.IsDeclarationFile && !useFileVersionAsSignature { - update.signature = h.computeDtsSignature(file) - } - // Default is to use file version as signature - if update.signature == "" { - update.signature = info.version - update.kind = SignatureUpdateKindUsedVersion - } - return update.signature != prevSignature -} - -func (h *affectedFilesHandler) getFilesAffectedBy(path tspath.Path) []*ast.SourceFile { - file := h.program.program.GetSourceFileByPath(path) - if file == nil { - return nil - } - - if !h.updateShapeSignature(file, false) { - return []*ast.SourceFile{file} - } - - if info, _ := h.program.snapshot.fileInfos.Load(file.Path()); info.affectsGlobalScope { - h.hasAllFilesExcludingDefaultLibraryFile.Store(true) - h.program.snapshot.getAllFilesExcludingDefaultLibraryFile(h.program.program, file) - } - - if h.program.snapshot.options.IsolatedModules.IsTrue() { - return []*ast.SourceFile{file} - } - - // Now we need to if each file in the referencedBy list has a shape change as well. - // Because if so, its own referencedBy files need to be saved as well to make the - // emitting result consistent with files on disk. - seenFileNamesMap := h.forEachFileReferencedBy( - file, - func(currentFile *ast.SourceFile, currentPath tspath.Path) (queueForFile bool, fastReturn bool) { - // If the current file is not nil and has a shape change, we need to queue it for processing - if currentFile != nil && h.updateShapeSignature(currentFile, false) { - return true, false - } - return false, false - }, - ) - // Return array of values that needs emit - return core.Filter(slices.Collect(maps.Values(seenFileNamesMap)), func(file *ast.SourceFile) bool { - return file != nil - }) -} - -func (h *affectedFilesHandler) forEachFileReferencedBy(file *ast.SourceFile, fn func(currentFile *ast.SourceFile, currentPath tspath.Path) (queueForFile bool, fastReturn bool)) map[tspath.Path]*ast.SourceFile { - // Now we need to if each file in the referencedBy list has a shape change as well. - // Because if so, its own referencedBy files need to be saved as well to make the - // emitting result consistent with files on disk. - seenFileNamesMap := map[tspath.Path]*ast.SourceFile{} - // Start with the paths this file was referenced by - seenFileNamesMap[file.Path()] = file - queue := slices.Collect(h.program.snapshot.referencedMap.getReferencedBy(file.Path())) - for len(queue) > 0 { - currentPath := queue[len(queue)-1] - queue = queue[:len(queue)-1] - if _, ok := seenFileNamesMap[currentPath]; !ok { - currentFile := h.program.program.GetSourceFileByPath(currentPath) - seenFileNamesMap[currentPath] = currentFile - queueForFile, fastReturn := fn(currentFile, currentPath) - if fastReturn { - return seenFileNamesMap - } - if queueForFile { - for ref := range h.program.snapshot.referencedMap.getReferencedBy(currentFile.Path()) { - queue = append(queue, ref) - } - } - } - } - return seenFileNamesMap -} - -// Handles semantic diagnostics and dts emit for affectedFile and files, that are referencing modules that export entities from affected file -// This is because even though js emit doesnt change, dts emit / type used can change resulting in need for dts emit and js change -func (h *affectedFilesHandler) handleDtsMayChangeOfAffectedFile(dtsMayChange dtsMayChange, affectedFile *ast.SourceFile) { - h.removeSemanticDiagnosticsOf(affectedFile.Path()) - - // If affected files is everything except default library, then nothing more to do - if h.hasAllFilesExcludingDefaultLibraryFile.Load() { - h.removeDiagnosticsOfLibraryFiles() - // When a change affects the global scope, all files are considered to be affected without updating their signature - // That means when affected file is handled, its signature can be out of date - // To avoid this, ensure that we update the signature for any affected file in this scenario. - h.updateShapeSignature(affectedFile, false) - return - } - - if h.program.snapshot.options.AssumeChangesOnlyAffectDirectDependencies.IsTrue() { - return - } - - // Iterate on referencing modules that export entities from affected file and delete diagnostics and add pending emit - // If there was change in signature (dts output) for the changed file, - // then only we need to handle pending file emit - if !h.program.snapshot.changedFilesSet.Has(affectedFile.Path()) || - !h.isChangedSignature(affectedFile.Path()) { - return - } - - // Since isolated modules dont change js files, files affected by change in signature is itself - // But we need to cleanup semantic diagnostics and queue dts emit for affected files - if h.program.snapshot.options.IsolatedModules.IsTrue() { - h.forEachFileReferencedBy( - affectedFile, - func(currentFile *ast.SourceFile, currentPath tspath.Path) (queueForFile bool, fastReturn bool) { - if h.handleDtsMayChangeOfGlobalScope(dtsMayChange, currentPath /*invalidateJsFiles*/, false) { - return false, true - } - h.handleDtsMayChangeOf(dtsMayChange, currentPath /*invalidateJsFiles*/, false) - if h.isChangedSignature(currentPath) { - return true, false - } - return false, false - }, - ) - } - - invalidateJsFiles := false - var typeChecker *checker.Checker - var done func() - // If exported const enum, we need to ensure that js files are emitted as well since the const enum value changed - if affectedFile.Symbol != nil { - for _, exported := range affectedFile.Symbol.Exports { - if exported.Flags&ast.SymbolFlagsConstEnum != 0 { - invalidateJsFiles = true - break - } - if typeChecker == nil { - typeChecker, done = h.program.program.GetTypeCheckerForFile(h.ctx, affectedFile) - } - aliased := checker.SkipAlias(exported, typeChecker) - if aliased == exported { - continue - } - if (aliased.Flags & ast.SymbolFlagsConstEnum) != 0 { - if slices.ContainsFunc(aliased.Declarations, func(d *ast.Node) bool { - return ast.GetSourceFileOfNode(d) == affectedFile - }) { - invalidateJsFiles = true - break - } - } - } - } - if done != nil { - done() - } - - // Go through files that reference affected file and handle dts emit and semantic diagnostics for them and their references - for exportedFromPath := range h.program.snapshot.referencedMap.getReferencedBy(affectedFile.Path()) { - if h.handleDtsMayChangeOfGlobalScope(dtsMayChange, exportedFromPath, invalidateJsFiles) { - return - } - for filePath := range h.program.snapshot.referencedMap.getReferencedBy(exportedFromPath) { - if h.handleDtsMayChangeOfFileAndExportsOfFile(dtsMayChange, filePath, invalidateJsFiles) { - return - } - } - } -} - -func (h *affectedFilesHandler) handleDtsMayChangeOfFileAndExportsOfFile(dtsMayChange dtsMayChange, filePath tspath.Path, invalidateJsFiles bool) bool { - if existing, loaded := h.seenFileAndExportsOfFile.LoadOrStore(filePath, invalidateJsFiles); loaded && (existing || !invalidateJsFiles) { - return false - } - if h.handleDtsMayChangeOfGlobalScope(dtsMayChange, filePath, invalidateJsFiles) { - return true - } - h.handleDtsMayChangeOf(dtsMayChange, filePath, invalidateJsFiles) - - // Remove the diagnostics of files that import this file and handle all its exports too - for referencingFilePath := range h.program.snapshot.referencedMap.getReferencedBy(filePath) { - if h.handleDtsMayChangeOfFileAndExportsOfFile(dtsMayChange, referencingFilePath, invalidateJsFiles) { - return true - } - } - return false -} - -func (h *affectedFilesHandler) handleDtsMayChangeOfGlobalScope(dtsMayChange dtsMayChange, filePath tspath.Path, invalidateJsFiles bool) bool { - if info, ok := h.program.snapshot.fileInfos.Load(filePath); !ok || !info.affectsGlobalScope { - return false - } - // Every file needs to be handled - for _, file := range h.program.snapshot.getAllFilesExcludingDefaultLibraryFile(h.program.program, nil) { - h.handleDtsMayChangeOf(dtsMayChange, file.Path(), invalidateJsFiles) - } - h.removeDiagnosticsOfLibraryFiles() - return true -} - -// Handle the dts may change, so they need to be added to pending emit if dts emit is enabled, -// Also we need to make sure signature is updated for these files -func (h *affectedFilesHandler) handleDtsMayChangeOf(dtsMayChange dtsMayChange, path tspath.Path, invalidateJsFiles bool) { - if h.program.snapshot.changedFilesSet.Has(path) { - return - } - file := h.program.program.GetSourceFileByPath(path) - if file == nil { - return - } - h.removeSemanticDiagnosticsOf(path) - // Even though the js emit doesnt change and we are already handling dts emit and semantic diagnostics - // we need to update the signature to reflect correctness of the signature(which is output d.ts emit) of this file - // This ensures that we dont later during incremental builds considering wrong signature. - // Eg where this also is needed to ensure that .tsbuildinfo generated by incremental build should be same as if it was first fresh build - // But we avoid expensive full shape computation, as using file version as shape is enough for correctness. - h.updateShapeSignature(file, true) - // If not dts emit, nothing more to do - if invalidateJsFiles { - dtsMayChange.addFileToAffectedFilesPendingEmit(path, GetFileEmitKind(h.program.snapshot.options)) - } else if h.program.snapshot.options.GetEmitDeclarations() { - dtsMayChange.addFileToAffectedFilesPendingEmit(path, core.IfElse(h.program.snapshot.options.DeclarationMap.IsTrue(), FileEmitKindAllDts, FileEmitKindDts)) - } -} - -func (h *affectedFilesHandler) updateSnapshot() { - if h.ctx.Err() != nil { - return - } - h.updatedSignatures.Range(func(filePath tspath.Path, update *updatedSignature) bool { - if info, ok := h.program.snapshot.fileInfos.Load(filePath); ok { - info.signature = update.signature - if h.program.testingData != nil { - h.program.testingData.UpdatedSignatureKinds[filePath] = update.kind - } - } - return true - }) - h.filesToRemoveDiagnostics.Range(func(file tspath.Path) bool { - h.program.snapshot.semanticDiagnosticsPerFile.Delete(file) - return true - }) - for _, change := range h.dtsMayChange { - for filePath, emitKind := range change { - h.program.snapshot.addFileToAffectedFilesPendingEmit(filePath, emitKind) - } - } - h.program.snapshot.changedFilesSet = collections.SyncSet[tspath.Path]{} - h.program.snapshot.buildInfoEmitPending.Store(true) -} - -func collectAllAffectedFiles(ctx context.Context, program *Program) { - if program.snapshot.changedFilesSet.Size() == 0 { - return - } - - handler := affectedFilesHandler{ctx: ctx, program: program} - wg := core.NewWorkGroup(handler.program.program.SingleThreaded()) - var result collections.SyncSet[*ast.SourceFile] - program.snapshot.changedFilesSet.Range(func(file tspath.Path) bool { - wg.Queue(func() { - for _, affectedFile := range handler.getFilesAffectedBy(file) { - result.Add(affectedFile) - } - }) - return true - }) - wg.RunAndWait() - - if ctx.Err() != nil { - return - } - - // For all the affected files, get all the files that would need to change their dts or js files, - // update their diagnostics - wg = core.NewWorkGroup(program.program.SingleThreaded()) - emitKind := GetFileEmitKind(program.snapshot.options) - result.Range(func(file *ast.SourceFile) bool { - // remove the cached semantic diagnostics and handle dts emit and js emit if needed - dtsMayChange := handler.getDtsMayChange(file.Path(), emitKind) - wg.Queue(func() { - handler.handleDtsMayChangeOfAffectedFile(dtsMayChange, file) - }) - return true - }) - wg.RunAndWait() - - // Update the snapshot with the new state - handler.updateSnapshot() -} diff --git a/kitcom/internal/tsgo/execute/incremental/buildInfo.go b/kitcom/internal/tsgo/execute/incremental/buildInfo.go deleted file mode 100644 index f0a8754..0000000 --- a/kitcom/internal/tsgo/execute/incremental/buildInfo.go +++ /dev/null @@ -1,584 +0,0 @@ -package incremental - -import ( - "fmt" - "iter" - - "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/tsoptions" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath" - "github.com/go-json-experiment/json" - "github.com/go-json-experiment/json/jsontext" -) - -type ( - BuildInfoFileId int - BuildInfoFileIdListId int -) - -// buildInfoRoot is -// - for incremental program buildinfo -// - start and end of FileId for consecutive fileIds to be included as root -// - start - single fileId that is root -// -// - for non incremental program buildinfo -// - string that is the root file name -type BuildInfoRoot struct { - Start BuildInfoFileId - End BuildInfoFileId - NonIncremental string // Root of a non incremental program -} - -func (b *BuildInfoRoot) MarshalJSON() ([]byte, error) { - if b.Start != 0 { - if b.End != 0 { - return json.Marshal([2]BuildInfoFileId{b.Start, b.End}) - } else { - return json.Marshal(b.Start) - } - } else { - return json.Marshal(b.NonIncremental) - } -} - -func (b *BuildInfoRoot) UnmarshalJSON(data []byte) error { - var startAndEnd *[2]int - if err := json.Unmarshal(data, &startAndEnd); err != nil { - var start int - if err := json.Unmarshal(data, &start); err != nil { - var name string - if err := json.Unmarshal(data, &name); err != nil { - return fmt.Errorf("invalid BuildInfoRoot: %s", data) - } - *b = BuildInfoRoot{ - NonIncremental: name, - } - return nil - } - *b = BuildInfoRoot{ - Start: BuildInfoFileId(start), - } - return nil - } - *b = BuildInfoRoot{ - Start: BuildInfoFileId(startAndEnd[0]), - End: BuildInfoFileId(startAndEnd[1]), - } - return nil -} - -type buildInfoFileInfoNoSignature struct { - Version string `json:"version,omitzero"` - NoSignature bool `json:"noSignature,omitzero"` - AffectsGlobalScope bool `json:"affectsGlobalScope,omitzero"` - ImpliedNodeFormat core.ResolutionMode `json:"impliedNodeFormat,omitzero"` -} - -// Signature is -// - undefined if FileInfo.version === FileInfo.signature -// - string actual signature -type buildInfoFileInfoWithSignature struct { - Version string `json:"version,omitzero"` - Signature string `json:"signature,omitzero"` - AffectsGlobalScope bool `json:"affectsGlobalScope,omitzero"` - ImpliedNodeFormat core.ResolutionMode `json:"impliedNodeFormat,omitzero"` -} - -type BuildInfoFileInfo struct { - signature string - noSignature *buildInfoFileInfoNoSignature - fileInfo *buildInfoFileInfoWithSignature -} - -func newBuildInfoFileInfo(fileInfo *fileInfo) *BuildInfoFileInfo { - if fileInfo.version == fileInfo.signature { - if !fileInfo.affectsGlobalScope && fileInfo.impliedNodeFormat == core.ResolutionModeCommonJS { - return &BuildInfoFileInfo{signature: fileInfo.signature} - } - } else if fileInfo.signature == "" { - return &BuildInfoFileInfo{noSignature: &buildInfoFileInfoNoSignature{ - Version: fileInfo.version, - NoSignature: true, - AffectsGlobalScope: fileInfo.affectsGlobalScope, - ImpliedNodeFormat: fileInfo.impliedNodeFormat, - }} - } - return &BuildInfoFileInfo{fileInfo: &buildInfoFileInfoWithSignature{ - Version: fileInfo.version, - Signature: core.IfElse(fileInfo.signature == fileInfo.version, "", fileInfo.signature), - AffectsGlobalScope: fileInfo.affectsGlobalScope, - ImpliedNodeFormat: fileInfo.impliedNodeFormat, - }} -} - -func (b *BuildInfoFileInfo) GetFileInfo() *fileInfo { - if b == nil { - return nil - } - if b.signature != "" { - return &fileInfo{ - version: b.signature, - signature: b.signature, - impliedNodeFormat: core.ResolutionModeCommonJS, - } - } - if b.noSignature != nil { - return &fileInfo{ - version: b.noSignature.Version, - affectsGlobalScope: b.noSignature.AffectsGlobalScope, - impliedNodeFormat: b.noSignature.ImpliedNodeFormat, - } - } - return &fileInfo{ - version: b.fileInfo.Version, - signature: core.IfElse(b.fileInfo.Signature == "", b.fileInfo.Version, b.fileInfo.Signature), - affectsGlobalScope: b.fileInfo.AffectsGlobalScope, - impliedNodeFormat: b.fileInfo.ImpliedNodeFormat, - } -} - -func (b *BuildInfoFileInfo) HasSignature() bool { - return b.signature != "" -} - -func (b *BuildInfoFileInfo) MarshalJSON() ([]byte, error) { - if b.signature != "" { - return json.Marshal(b.signature) - } - if b.noSignature != nil { - return json.Marshal(b.noSignature) - } - return json.Marshal(b.fileInfo) -} - -func (b *BuildInfoFileInfo) UnmarshalJSON(data []byte) error { - var vSignature string - if err := json.Unmarshal(data, &vSignature); err != nil { - var noSignature buildInfoFileInfoNoSignature - if err := json.Unmarshal(data, &noSignature); err != nil || !noSignature.NoSignature { - var fileInfo buildInfoFileInfoWithSignature - if err := json.Unmarshal(data, &fileInfo); err != nil { - return fmt.Errorf("invalid BuildInfoFileInfo: %s", data) - } - *b = BuildInfoFileInfo{fileInfo: &fileInfo} - return nil - } - *b = BuildInfoFileInfo{noSignature: &noSignature} - return nil - } - *b = BuildInfoFileInfo{signature: vSignature} - return nil -} - -type BuildInfoReferenceMapEntry struct { - FileId BuildInfoFileId - FileIdListId BuildInfoFileIdListId -} - -func (b *BuildInfoReferenceMapEntry) MarshalJSON() ([]byte, error) { - return json.Marshal([2]int{int(b.FileId), int(b.FileIdListId)}) -} - -func (b *BuildInfoReferenceMapEntry) UnmarshalJSON(data []byte) error { - var v *[2]int - if err := json.Unmarshal(data, &v); err != nil { - return err - } - *b = BuildInfoReferenceMapEntry{ - FileId: BuildInfoFileId(v[0]), - FileIdListId: BuildInfoFileIdListId(v[1]), - } - return nil -} - -type BuildInfoDiagnostic struct { - // BuildInfoFileId if it is for a File thats other than its stored for - File BuildInfoFileId `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 []*BuildInfoDiagnostic `json:"messageChain,omitzero"` - RelatedInformation []*BuildInfoDiagnostic `json:"relatedInformation,omitzero"` - ReportsUnnecessary bool `json:"reportsUnnecessary,omitzero"` - ReportsDeprecated bool `json:"reportsDeprecated,omitzero"` - SkippedOnNoEmit bool `json:"skippedOnNoEmit,omitzero"` -} - -type BuildInfoDiagnosticsOfFile struct { - FileId BuildInfoFileId - Diagnostics []*BuildInfoDiagnostic -} - -func (b *BuildInfoDiagnosticsOfFile) MarshalJSON() ([]byte, error) { - fileIdAndDiagnostics := make([]any, 0, 2) - fileIdAndDiagnostics = append(fileIdAndDiagnostics, b.FileId) - fileIdAndDiagnostics = append(fileIdAndDiagnostics, b.Diagnostics) - return json.Marshal(fileIdAndDiagnostics) -} - -func (b *BuildInfoDiagnosticsOfFile) UnmarshalJSON(data []byte) error { - var fileIdAndDiagnostics []jsontext.Value - if err := json.Unmarshal(data, &fileIdAndDiagnostics); err != nil { - return fmt.Errorf("invalid BuildInfoDiagnosticsOfFile: %s", data) - } - if len(fileIdAndDiagnostics) != 2 { - return fmt.Errorf("invalid BuildInfoDiagnosticsOfFile: expected 2 elements, got %d", len(fileIdAndDiagnostics)) - } - var fileId BuildInfoFileId - if err := json.Unmarshal(fileIdAndDiagnostics[0], &fileId); err != nil { - return fmt.Errorf("invalid fileId in BuildInfoDiagnosticsOfFile: %w", err) - } - - var diagnostics []*BuildInfoDiagnostic - if err := json.Unmarshal(fileIdAndDiagnostics[1], &diagnostics); err != nil { - return fmt.Errorf("invalid diagnostics in BuildInfoDiagnosticsOfFile: %w", err) - } - *b = BuildInfoDiagnosticsOfFile{ - FileId: fileId, - Diagnostics: diagnostics, - } - return nil -} - -type BuildInfoSemanticDiagnostic struct { - FileId BuildInfoFileId // File is not in changedSet and still doesnt have cached diagnostics - Diagnostics *BuildInfoDiagnosticsOfFile // Diagnostics for file -} - -func (b *BuildInfoSemanticDiagnostic) MarshalJSON() ([]byte, error) { - if b.FileId != 0 { - return json.Marshal(b.FileId) - } - return json.Marshal(b.Diagnostics) -} - -func (b *BuildInfoSemanticDiagnostic) UnmarshalJSON(data []byte) error { - var fileId BuildInfoFileId - if err := json.Unmarshal(data, &fileId); err != nil { - var diagnostics BuildInfoDiagnosticsOfFile - if err := json.Unmarshal(data, &diagnostics); err != nil { - return fmt.Errorf("invalid BuildInfoSemanticDiagnostic: %s", data) - } - *b = BuildInfoSemanticDiagnostic{ - Diagnostics: &diagnostics, - } - return nil - } - *b = BuildInfoSemanticDiagnostic{ - FileId: fileId, - } - return nil -} - -// fileId if pending emit is same as what compilerOptions suggest -// [fileId] if pending emit is only dts file emit -// [fileId, emitKind] if any other type emit is pending -type BuildInfoFilePendingEmit struct { - FileId BuildInfoFileId - EmitKind FileEmitKind -} - -func (b *BuildInfoFilePendingEmit) MarshalJSON() ([]byte, error) { - if b.EmitKind == 0 { - return json.Marshal(b.FileId) - } - if b.EmitKind == FileEmitKindDts { - fileListIds := []BuildInfoFileId{b.FileId} - return json.Marshal(fileListIds) - } - fileAndEmitKind := []int{int(b.FileId), int(b.EmitKind)} - return json.Marshal(fileAndEmitKind) -} - -func (b *BuildInfoFilePendingEmit) UnmarshalJSON(data []byte) error { - var fileId BuildInfoFileId - if err := json.Unmarshal(data, &fileId); err != nil { - var intTuple []int - if err := json.Unmarshal(data, &intTuple); err != nil || len(intTuple) == 0 { - return fmt.Errorf("invalid BuildInfoFilePendingEmit: %s", data) - } - switch len(intTuple) { - case 1: - *b = BuildInfoFilePendingEmit{ - FileId: BuildInfoFileId(intTuple[0]), - EmitKind: FileEmitKindDts, - } - return nil - case 2: - *b = BuildInfoFilePendingEmit{ - FileId: BuildInfoFileId(intTuple[0]), - EmitKind: FileEmitKind(intTuple[1]), - } - return nil - default: - return fmt.Errorf("invalid BuildInfoFilePendingEmit: expected 1 or 2 integers, got %d", len(intTuple)) - } - } - *b = BuildInfoFilePendingEmit{ - FileId: fileId, - } - return nil -} - -// [fileId, signature] if different from file's signature -// fileId if file wasnt emitted -type BuildInfoEmitSignature struct { - FileId BuildInfoFileId - Signature string // Signature if it is different from file's Signature - DiffersOnlyInDtsMap bool // true if signature is different only in dtsMap value - DiffersInOptions bool // true if signature is different in options used to emit file -} - -func (b *BuildInfoEmitSignature) noEmitSignature() bool { - return b.Signature == "" && !b.DiffersOnlyInDtsMap && !b.DiffersInOptions -} - -func (b *BuildInfoEmitSignature) toEmitSignature(path tspath.Path, emitSignatures *collections.SyncMap[tspath.Path, *emitSignature]) *emitSignature { - var signature string - var signatureWithDifferentOptions []string - if b.DiffersOnlyInDtsMap { - signatureWithDifferentOptions = make([]string, 0, 1) - info, _ := emitSignatures.Load(path) - signatureWithDifferentOptions = append(signatureWithDifferentOptions, info.signature) - } else if b.DiffersInOptions { - signatureWithDifferentOptions = make([]string, 0, 1) - signatureWithDifferentOptions = append(signatureWithDifferentOptions, b.Signature) - } else { - signature = b.Signature - } - return &emitSignature{ - signature: signature, - signatureWithDifferentOptions: signatureWithDifferentOptions, - } -} - -func (b *BuildInfoEmitSignature) MarshalJSON() ([]byte, error) { - if b.noEmitSignature() { - return json.Marshal(b.FileId) - } - fileIdAndSignature := make([]any, 2) - fileIdAndSignature[0] = b.FileId - var signature any - if b.DiffersOnlyInDtsMap { - signature = []string{} - } else if b.DiffersInOptions { - signature = []string{b.Signature} - } else { - signature = b.Signature - } - fileIdAndSignature[1] = signature - return json.Marshal(fileIdAndSignature) -} - -func (b *BuildInfoEmitSignature) UnmarshalJSON(data []byte) error { - var fileId BuildInfoFileId - if err := json.Unmarshal(data, &fileId); err != nil { - var fileIdAndSignature []any - if err := json.Unmarshal(data, &fileIdAndSignature); err != nil { - return fmt.Errorf("invalid BuildInfoEmitSignature: %s", data) - } - if len(fileIdAndSignature) != 2 { - return fmt.Errorf("invalid BuildInfoEmitSignature: expected 2 elements, got %d", len(fileIdAndSignature)) - } - var fileId BuildInfoFileId - if id, ok := fileIdAndSignature[0].(float64); !ok { - return fmt.Errorf("invalid fileId in BuildInfoEmitSignature: expected float64, got %T", fileIdAndSignature[0]) - } else { - fileId = BuildInfoFileId(id) - } - var signature string - var differsOnlyInDtsMap, differsInOptions bool - if signatureV, ok := fileIdAndSignature[1].(string); !ok { - if signatureList, ok := fileIdAndSignature[1].([]any); !ok { - return fmt.Errorf("invalid signature in BuildInfoEmitSignature: expected string or []string, got %T", fileIdAndSignature[1]) - } else { - switch len(signatureList) { - case 0: - differsOnlyInDtsMap = true - case 1: - if sig, ok := signatureList[0].(string); !ok { - return fmt.Errorf("invalid signature in BuildInfoEmitSignature: expected string, got %T", signatureList[0]) - } else { - signature = sig - differsInOptions = true - } - default: - return fmt.Errorf("invalid signature in BuildInfoEmitSignature: expected string or []string with 0 or 1 element, got %d elements", len(signatureList)) - } - } - } else { - signature = signatureV - } - *b = BuildInfoEmitSignature{ - FileId: fileId, - Signature: signature, - DiffersOnlyInDtsMap: differsOnlyInDtsMap, - DiffersInOptions: differsInOptions, - } - return nil - - } - *b = BuildInfoEmitSignature{ - FileId: fileId, - } - return nil -} - -type BuildInfoResolvedRoot struct { - Resolved BuildInfoFileId - Root BuildInfoFileId -} - -func (b *BuildInfoResolvedRoot) MarshalJSON() ([]byte, error) { - return json.Marshal([2]BuildInfoFileId{b.Resolved, b.Root}) -} - -func (b *BuildInfoResolvedRoot) UnmarshalJSON(data []byte) error { - var resolvedAndRoot [2]int - if err := json.Unmarshal(data, &resolvedAndRoot); err != nil { - return fmt.Errorf("invalid BuildInfoResolvedRoot: %s", data) - } - *b = BuildInfoResolvedRoot{ - Resolved: BuildInfoFileId(resolvedAndRoot[0]), - Root: BuildInfoFileId(resolvedAndRoot[1]), - } - return nil -} - -type BuildInfo struct { - 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 []*BuildInfoRoot `json:"root,omitzero"` - - // IncrementalProgram info - FileNames []string `json:"fileNames,omitzero"` - FileInfos []*BuildInfoFileInfo `json:"fileInfos,omitzero"` - FileIdsList [][]BuildInfoFileId `json:"fileIdsList,omitzero"` - Options *collections.OrderedMap[string, any] `json:"options,omitzero"` - ReferencedMap []*BuildInfoReferenceMapEntry `json:"referencedMap,omitzero"` - SemanticDiagnosticsPerFile []*BuildInfoSemanticDiagnostic `json:"semanticDiagnosticsPerFile,omitzero"` - EmitDiagnosticsPerFile []*BuildInfoDiagnosticsOfFile `json:"emitDiagnosticsPerFile,omitzero"` - ChangeFileSet []BuildInfoFileId `json:"changeFileSet,omitzero"` - AffectedFilesPendingEmit []*BuildInfoFilePendingEmit `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 []*BuildInfoEmitSignature `json:"emitSignatures,omitzero"` - ResolvedRoot []*BuildInfoResolvedRoot `json:"resolvedRoot,omitzero"` - - // NonIncrementalProgram info - SemanticErrors bool `json:"semanticErrors,omitzero"` -} - -func (b *BuildInfo) IsValidVersion() bool { - return b.Version == core.Version() -} - -func (b *BuildInfo) IsIncremental() bool { - return b != nil && len(b.FileNames) != 0 -} - -func (b *BuildInfo) fileName(fileId BuildInfoFileId) string { - return b.FileNames[fileId-1] -} - -func (b *BuildInfo) fileInfo(fileId BuildInfoFileId) *BuildInfoFileInfo { - return b.FileInfos[fileId-1] -} - -func (b *BuildInfo) GetCompilerOptions(buildInfoDirectory string) *core.CompilerOptions { - options := &core.CompilerOptions{} - for option, value := range b.Options.Entries() { - if buildInfoDirectory != "" { - result, ok := tsoptions.ConvertOptionToAbsolutePath(option, value, tsoptions.CommandLineCompilerOptionsMap, buildInfoDirectory) - if ok { - tsoptions.ParseCompilerOptions(option, result, options) - continue - } - } - tsoptions.ParseCompilerOptions(option, value, options) - - } - return options -} - -func (b *BuildInfo) IsEmitPending(resolved *tsoptions.ParsedCommandLine, buildInfoDirectory string) bool { - // Some of the emit files like source map or dts etc are not yet done - if !resolved.CompilerOptions().NoEmit.IsTrue() || resolved.CompilerOptions().GetEmitDeclarations() { - pendingEmit := getPendingEmitKindWithOptions(resolved.CompilerOptions(), b.GetCompilerOptions(buildInfoDirectory)) - if resolved.CompilerOptions().NoEmit.IsTrue() { - pendingEmit &= FileEmitKindDtsErrors - } - return pendingEmit != 0 - } - return false -} - -func (b *BuildInfo) GetBuildInfoRootInfoReader(buildInfoDirectory string, comparePathOptions tspath.ComparePathsOptions) *BuildInfoRootInfoReader { - resolvedRootFileInfos := make(map[tspath.Path]*BuildInfoFileInfo, len(b.FileNames)) - // Roots of the File - rootToResolved := collections.NewOrderedMapWithSizeHint[tspath.Path, tspath.Path](len(b.FileNames)) - resolvedToRoot := make(map[tspath.Path]tspath.Path, len(b.ResolvedRoot)) - toPath := func(fileName string) tspath.Path { - return tspath.ToPath(fileName, buildInfoDirectory, comparePathOptions.UseCaseSensitiveFileNames) - } - - // Create map from resolvedRoot to Root - for _, resolved := range b.ResolvedRoot { - resolvedToRoot[toPath(b.fileName(resolved.Resolved))] = toPath(b.fileName(resolved.Root)) - } - - addRoot := func(resolvedRoot string, fileInfo *BuildInfoFileInfo) { - resolvedRootPath := toPath(resolvedRoot) - if rootPath, ok := resolvedToRoot[resolvedRootPath]; ok { - rootToResolved.Set(rootPath, resolvedRootPath) - } else { - rootToResolved.Set(resolvedRootPath, resolvedRootPath) - } - if fileInfo != nil { - resolvedRootFileInfos[resolvedRootPath] = fileInfo - } - } - - for _, root := range b.Root { - if root.NonIncremental != "" { - addRoot(root.NonIncremental, nil) - } else if root.End == 0 { - addRoot(b.fileName(root.Start), b.fileInfo(root.Start)) - } else { - for i := root.Start; i <= root.End; i++ { - addRoot(b.fileName(i), b.fileInfo(i)) - } - } - } - - return &BuildInfoRootInfoReader{ - resolvedRootFileInfos: resolvedRootFileInfos, - rootToResolved: rootToResolved, - } -} - -type BuildInfoRootInfoReader struct { - resolvedRootFileInfos map[tspath.Path]*BuildInfoFileInfo - rootToResolved *collections.OrderedMap[tspath.Path, tspath.Path] -} - -func (b *BuildInfoRootInfoReader) GetBuildInfoFileInfo(inputFilePath tspath.Path) (*BuildInfoFileInfo, tspath.Path) { - if info, ok := b.resolvedRootFileInfos[inputFilePath]; ok { - return info, inputFilePath - } - if resolved, ok := b.rootToResolved.Get(inputFilePath); ok { - return b.resolvedRootFileInfos[resolved], resolved - } - return nil, "" -} - -func (b *BuildInfoRootInfoReader) Roots() iter.Seq[tspath.Path] { - return b.rootToResolved.Keys() -} diff --git a/kitcom/internal/tsgo/execute/incremental/buildinfotosnapshot.go b/kitcom/internal/tsgo/execute/incremental/buildinfotosnapshot.go deleted file mode 100644 index 47186b7..0000000 --- a/kitcom/internal/tsgo/execute/incremental/buildinfotosnapshot.go +++ /dev/null @@ -1,171 +0,0 @@ -package incremental - -import ( - "strings" - - "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 buildInfoToSnapshot(buildInfo *BuildInfo, config *tsoptions.ParsedCommandLine, host compiler.CompilerHost) *snapshot { - to := &toSnapshot{ - buildInfo: buildInfo, - buildInfoDirectory: tspath.GetDirectoryPath(tspath.GetNormalizedAbsolutePath(config.GetBuildInfoFileName(), config.GetCurrentDirectory())), - filePaths: make([]tspath.Path, 0, len(buildInfo.FileNames)), - filePathSet: make([]*collections.Set[tspath.Path], 0, len(buildInfo.FileIdsList)), - } - to.filePaths = core.Map(buildInfo.FileNames, func(fileName string) tspath.Path { - if !strings.HasPrefix(fileName, ".") { - return tspath.ToPath(tspath.CombinePaths(host.DefaultLibraryPath(), fileName), host.GetCurrentDirectory(), host.FS().UseCaseSensitiveFileNames()) - } - return tspath.ToPath(fileName, to.buildInfoDirectory, config.UseCaseSensitiveFileNames()) - }) - to.filePathSet = core.Map(buildInfo.FileIdsList, func(fileIdList []BuildInfoFileId) *collections.Set[tspath.Path] { - fileSet := collections.NewSetWithSizeHint[tspath.Path](len(fileIdList)) - for _, fileId := range fileIdList { - fileSet.Add(to.toFilePath(fileId)) - } - return fileSet - }) - to.setCompilerOptions() - to.setFileInfoAndEmitSignatures() - to.setReferencedMap() - to.setChangeFileSet() - to.setSemanticDiagnostics() - to.setEmitDiagnostics() - to.setAffectedFilesPendingEmit() - if buildInfo.LatestChangedDtsFile != "" { - to.snapshot.latestChangedDtsFile = to.toAbsolutePath(buildInfo.LatestChangedDtsFile) - } - to.snapshot.hasErrors = core.IfElse(buildInfo.Errors, core.TSTrue, core.TSFalse) - to.snapshot.hasSemanticErrors = buildInfo.SemanticErrors - to.snapshot.checkPending = buildInfo.CheckPending - return &to.snapshot -} - -type toSnapshot struct { - buildInfo *BuildInfo - buildInfoDirectory string - snapshot snapshot - filePaths []tspath.Path - filePathSet []*collections.Set[tspath.Path] -} - -func (t *toSnapshot) toAbsolutePath(path string) string { - return tspath.GetNormalizedAbsolutePath(path, t.buildInfoDirectory) -} - -func (t *toSnapshot) toFilePath(fileId BuildInfoFileId) tspath.Path { - return t.filePaths[fileId-1] -} - -func (t *toSnapshot) toFilePathSet(fileIdListId BuildInfoFileIdListId) *collections.Set[tspath.Path] { - return t.filePathSet[fileIdListId-1] -} - -func (t *toSnapshot) toBuildInfoDiagnosticsWithFileName(diagnostics []*BuildInfoDiagnostic) []*buildInfoDiagnosticWithFileName { - return core.Map(diagnostics, func(d *BuildInfoDiagnostic) *buildInfoDiagnosticWithFileName { - var file tspath.Path - if d.File != 0 { - file = t.toFilePath(d.File) - } - return &buildInfoDiagnosticWithFileName{ - file: file, - noFile: d.NoFile, - pos: d.Pos, - end: d.End, - code: d.Code, - category: d.Category, - message: d.Message, - messageChain: t.toBuildInfoDiagnosticsWithFileName(d.MessageChain), - relatedInformation: t.toBuildInfoDiagnosticsWithFileName(d.RelatedInformation), - reportsUnnecessary: d.ReportsUnnecessary, - reportsDeprecated: d.ReportsDeprecated, - skippedOnNoEmit: d.SkippedOnNoEmit, - } - }) -} - -func (t *toSnapshot) toDiagnosticsOrBuildInfoDiagnosticsWithFileName(dig *BuildInfoDiagnosticsOfFile) *diagnosticsOrBuildInfoDiagnosticsWithFileName { - return &diagnosticsOrBuildInfoDiagnosticsWithFileName{ - buildInfoDiagnostics: t.toBuildInfoDiagnosticsWithFileName(dig.Diagnostics), - } -} - -func (t *toSnapshot) setCompilerOptions() { - t.snapshot.options = t.buildInfo.GetCompilerOptions(t.buildInfoDirectory) -} - -func (t *toSnapshot) setFileInfoAndEmitSignatures() { - isComposite := t.snapshot.options.Composite.IsTrue() - for index, buildInfoFileInfo := range t.buildInfo.FileInfos { - path := t.toFilePath(BuildInfoFileId(index + 1)) - info := buildInfoFileInfo.GetFileInfo() - t.snapshot.fileInfos.Store(path, info) - // Add default emit signature as file's signature - if info.signature != "" && isComposite { - t.snapshot.emitSignatures.Store(path, &emitSignature{signature: info.signature}) - } - } - // Fix up emit signatures - for _, value := range t.buildInfo.EmitSignatures { - if value.noEmitSignature() { - t.snapshot.emitSignatures.Delete(t.toFilePath(value.FileId)) - } else { - path := t.toFilePath(value.FileId) - t.snapshot.emitSignatures.Store(path, value.toEmitSignature(path, &t.snapshot.emitSignatures)) - } - } -} - -func (t *toSnapshot) setReferencedMap() { - for _, entry := range t.buildInfo.ReferencedMap { - t.snapshot.referencedMap.storeReferences(t.toFilePath(entry.FileId), t.toFilePathSet(entry.FileIdListId)) - } -} - -func (t *toSnapshot) setChangeFileSet() { - for _, fileId := range t.buildInfo.ChangeFileSet { - filePath := t.toFilePath(fileId) - t.snapshot.changedFilesSet.Add(filePath) - } -} - -func (t *toSnapshot) setSemanticDiagnostics() { - t.snapshot.fileInfos.Range(func(path tspath.Path, info *fileInfo) bool { - // Initialize to have no diagnostics if its not changed file - if !t.snapshot.changedFilesSet.Has(path) { - t.snapshot.semanticDiagnosticsPerFile.Store(path, &diagnosticsOrBuildInfoDiagnosticsWithFileName{}) - } - return true - }) - for _, diagnostic := range t.buildInfo.SemanticDiagnosticsPerFile { - if diagnostic.FileId != 0 { - filePath := t.toFilePath(diagnostic.FileId) - t.snapshot.semanticDiagnosticsPerFile.Delete(filePath) // does not have cached diagnostics - } else { - filePath := t.toFilePath(diagnostic.Diagnostics.FileId) - t.snapshot.semanticDiagnosticsPerFile.Store(filePath, t.toDiagnosticsOrBuildInfoDiagnosticsWithFileName(diagnostic.Diagnostics)) - } - } -} - -func (t *toSnapshot) setEmitDiagnostics() { - for _, diagnostic := range t.buildInfo.EmitDiagnosticsPerFile { - filePath := t.toFilePath(diagnostic.FileId) - t.snapshot.emitDiagnosticsPerFile.Store(filePath, t.toDiagnosticsOrBuildInfoDiagnosticsWithFileName(diagnostic)) - } -} - -func (t *toSnapshot) setAffectedFilesPendingEmit() { - if len(t.buildInfo.AffectedFilesPendingEmit) == 0 { - return - } - ownOptionsEmitKind := GetFileEmitKind(t.snapshot.options) - for _, pendingEmit := range t.buildInfo.AffectedFilesPendingEmit { - t.snapshot.affectedFilesPendingEmit.Store(t.toFilePath(pendingEmit.FileId), core.IfElse(pendingEmit.EmitKind == 0, ownOptionsEmitKind, pendingEmit.EmitKind)) - } -} diff --git a/kitcom/internal/tsgo/execute/incremental/emitfileshandler.go b/kitcom/internal/tsgo/execute/incremental/emitfileshandler.go deleted file mode 100644 index 46f7ef3..0000000 --- a/kitcom/internal/tsgo/execute/incremental/emitfileshandler.go +++ /dev/null @@ -1,329 +0,0 @@ -package incremental - -import ( - "context" - "sync/atomic" - "time" - - "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/tspath" -) - -type emitUpdate struct { - pendingKind FileEmitKind - result *compiler.EmitResult - dtsErrorsFromCache bool -} - -type emitFilesHandler struct { - ctx context.Context - program *Program - isForDtsErrors bool - signatures collections.SyncMap[tspath.Path, string] - emitSignatures collections.SyncMap[tspath.Path, *emitSignature] - latestChangedDtsFiles collections.SyncMap[tspath.Path, string] - deletedPendingKinds collections.Set[tspath.Path] - emitUpdates collections.SyncMap[tspath.Path, *emitUpdate] - hasEmitDiagnostics atomic.Bool -} - -// Determining what all is pending to be emitted based on previous options or previous file emit flags -func (h *emitFilesHandler) getPendingEmitKindForEmitOptions(emitKind FileEmitKind, options compiler.EmitOptions) FileEmitKind { - pendingKind := getPendingEmitKind(emitKind, 0) - if options.EmitOnly == compiler.EmitOnlyDts { - pendingKind &= FileEmitKindAllDts - } - if h.isForDtsErrors { - pendingKind &= FileEmitKindDtsErrors - } - return pendingKind -} - -// Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete -// The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host -// in that order would be used to write the files -func (h *emitFilesHandler) emitAllAffectedFiles(options compiler.EmitOptions) *compiler.EmitResult { - // Emit all affected files - if h.program.snapshot.canUseIncrementalState() { - results := h.emitFilesIncremental(options) - if h.isForDtsErrors { - if options.TargetSourceFile != nil { - // Result from cache - diagnostics, _ := h.program.snapshot.emitDiagnosticsPerFile.Load(options.TargetSourceFile.Path()) - return &compiler.EmitResult{ - EmitSkipped: true, - Diagnostics: diagnostics.getDiagnostics(h.program.program, options.TargetSourceFile), - } - } - return compiler.CombineEmitResults(results) - } else { - // Combine results and update buildInfo - result := compiler.CombineEmitResults(results) - h.emitBuildInfo(options, result) - return result - } - } else if !h.isForDtsErrors { - result := h.program.program.Emit(h.ctx, h.getEmitOptions(options)) - h.updateSnapshot() - h.emitBuildInfo(options, result) - return result - } else { - result := &compiler.EmitResult{ - EmitSkipped: true, - Diagnostics: h.program.program.GetDeclarationDiagnostics(h.ctx, options.TargetSourceFile), - } - if len(result.Diagnostics) != 0 { - h.program.snapshot.hasEmitDiagnostics = true - } - return result - } -} - -func (h *emitFilesHandler) emitBuildInfo(options compiler.EmitOptions, result *compiler.EmitResult) { - buildInfoResult := h.program.emitBuildInfo(h.ctx, options) - if buildInfoResult != nil { - result.Diagnostics = append(result.Diagnostics, buildInfoResult.Diagnostics...) - result.EmittedFiles = append(result.EmittedFiles, buildInfoResult.EmittedFiles...) - } -} - -func (h *emitFilesHandler) emitFilesIncremental(options compiler.EmitOptions) []*compiler.EmitResult { - // Get all affected files - collectAllAffectedFiles(h.ctx, h.program) - if h.ctx.Err() != nil { - return nil - } - - wg := core.NewWorkGroup(h.program.program.SingleThreaded()) - h.program.snapshot.affectedFilesPendingEmit.Range(func(path tspath.Path, emitKind FileEmitKind) bool { - affectedFile := h.program.program.GetSourceFileByPath(path) - if affectedFile == nil || !h.program.program.SourceFileMayBeEmitted(affectedFile, false) { - h.deletedPendingKinds.Add(path) - return true - } - pendingKind := h.getPendingEmitKindForEmitOptions(emitKind, options) - if pendingKind != 0 { - wg.Queue(func() { - // Determine if we can do partial emit - var emitOnly compiler.EmitOnly - if (pendingKind & FileEmitKindAllJs) != 0 { - emitOnly = compiler.EmitOnlyJs - } - if (pendingKind & FileEmitKindAllDts) != 0 { - if emitOnly == compiler.EmitOnlyJs { - emitOnly = compiler.EmitAll - } else { - emitOnly = compiler.EmitOnlyDts - } - } - var result *compiler.EmitResult - if !h.isForDtsErrors { - result = h.program.program.Emit(h.ctx, h.getEmitOptions(compiler.EmitOptions{ - TargetSourceFile: affectedFile, - EmitOnly: emitOnly, - WriteFile: options.WriteFile, - })) - } else { - result = &compiler.EmitResult{ - EmitSkipped: true, - Diagnostics: h.program.program.GetDeclarationDiagnostics(h.ctx, affectedFile), - } - } - - // Update the pendingEmit for the file - h.emitUpdates.Store(path, &emitUpdate{pendingKind: getPendingEmitKind(emitKind, pendingKind), result: result}) - }) - } - return true - }) - wg.RunAndWait() - if h.ctx.Err() != nil { - return nil - } - - // Get updated errors that were not included in affected files emit - h.program.snapshot.emitDiagnosticsPerFile.Range(func(path tspath.Path, diagnostics *diagnosticsOrBuildInfoDiagnosticsWithFileName) bool { - if _, ok := h.emitUpdates.Load(path); !ok { - affectedFile := h.program.program.GetSourceFileByPath(path) - if affectedFile == nil || !h.program.program.SourceFileMayBeEmitted(affectedFile, false) { - h.deletedPendingKinds.Add(path) - return true - } - pendingKind, _ := h.program.snapshot.affectedFilesPendingEmit.Load(path) - h.emitUpdates.Store(path, &emitUpdate{ - pendingKind: pendingKind, - result: &compiler.EmitResult{ - EmitSkipped: true, - Diagnostics: diagnostics.getDiagnostics(h.program.program, affectedFile), - }, - dtsErrorsFromCache: true, - }) - } - return true - }) - - return h.updateSnapshot() -} - -func (h *emitFilesHandler) getEmitOptions(options compiler.EmitOptions) compiler.EmitOptions { - if !h.program.snapshot.options.GetEmitDeclarations() { - return options - } - canUseIncrementalState := h.program.snapshot.canUseIncrementalState() - return compiler.EmitOptions{ - TargetSourceFile: options.TargetSourceFile, - EmitOnly: options.EmitOnly, - WriteFile: func(fileName string, text string, writeByteOrderMark bool, data *compiler.WriteFileData) error { - var differsOnlyInMap bool - if tspath.IsDeclarationFileName(fileName) { - if canUseIncrementalState { - var emitSignature string - info, _ := h.program.snapshot.fileInfos.Load(options.TargetSourceFile.Path()) - if info.signature == info.version { - signature := h.program.snapshot.computeSignatureWithDiagnostics(options.TargetSourceFile, text, data) - // With d.ts diagnostics they are also part of the signature so emitSignature will be different from it since its just hash of d.ts - if len(data.Diagnostics) == 0 { - emitSignature = signature - } - if signature != info.version { // Update it - h.signatures.Store(options.TargetSourceFile.Path(), signature) - } - } - - // Store d.ts emit hash so later can be compared to check if d.ts has changed. - // Currently we do this only for composite projects since these are the only projects that can be referenced by other projects - // and would need their d.ts change time in --build mode - if h.skipDtsOutputOfComposite(options.TargetSourceFile, fileName, text, data, emitSignature, &differsOnlyInMap) { - return nil - } - } else if len(data.Diagnostics) > 0 { - h.hasEmitDiagnostics.Store(true) - } - } - - var aTime time.Time - if differsOnlyInMap { - aTime = h.program.host.GetMTime(fileName) - } - var err error - if options.WriteFile != nil { - err = options.WriteFile(fileName, text, writeByteOrderMark, data) - } else { - err = h.program.program.Host().FS().WriteFile(fileName, text, writeByteOrderMark) - } - if err == nil && differsOnlyInMap { - // Revert the time to original one - err = h.program.host.SetMTime(fileName, aTime) - } - return err - }, - } -} - -// Compare to existing computed signature and store it or handle the changes in d.ts map option from before -// returning undefined means that, we dont need to emit this d.ts file since its contents didnt change -func (h *emitFilesHandler) skipDtsOutputOfComposite(file *ast.SourceFile, outputFileName string, text string, data *compiler.WriteFileData, newSignature string, differsOnlyInMap *bool) bool { - if !h.program.snapshot.options.Composite.IsTrue() { - return false - } - var oldSignature string - oldSignatureFormat, ok := h.program.snapshot.emitSignatures.Load(file.Path()) - if ok { - if oldSignatureFormat.signature != "" { - oldSignature = oldSignatureFormat.signature - } else { - oldSignature = oldSignatureFormat.signatureWithDifferentOptions[0] - } - } - if newSignature == "" { - newSignature = h.program.snapshot.computeHash(getTextHandlingSourceMapForSignature(text, data)) - } - // Dont write dts files if they didn't change - if newSignature == oldSignature { - // If the signature was encoded as string the dts map options match so nothing to do - if oldSignatureFormat != nil && oldSignatureFormat.signature == oldSignature { - data.SkippedDtsWrite = true - return true - } else { - // Mark as differsOnlyInMap so that we can reverse the timestamp with --build so that - // the downstream projects dont detect this as change in d.ts file - *differsOnlyInMap = h.program.Options().Build.IsTrue() - } - } else { - h.latestChangedDtsFiles.Store(file.Path(), outputFileName) - } - h.emitSignatures.Store(file.Path(), &emitSignature{signature: newSignature}) - return false -} - -func (h *emitFilesHandler) updateSnapshot() []*compiler.EmitResult { - if h.program.snapshot.canUseIncrementalState() { - h.signatures.Range(func(file tspath.Path, signature string) bool { - info, _ := h.program.snapshot.fileInfos.Load(file) - info.signature = signature - if h.program.testingData != nil { - h.program.testingData.UpdatedSignatureKinds[file] = SignatureUpdateKindStoredAtEmit - } - h.program.snapshot.buildInfoEmitPending.Store(true) - return true - }) - h.emitSignatures.Range(func(file tspath.Path, signature *emitSignature) bool { - h.program.snapshot.emitSignatures.Store(file, signature) - h.program.snapshot.buildInfoEmitPending.Store(true) - return true - }) - for file := range h.deletedPendingKinds.Keys() { - h.program.snapshot.affectedFilesPendingEmit.Delete(file) - h.program.snapshot.buildInfoEmitPending.Store(true) - } - // Always use correct order when to collect the result - var results []*compiler.EmitResult - for _, file := range h.program.GetSourceFiles() { - if latestChangedDtsFile, ok := h.latestChangedDtsFiles.Load(file.Path()); ok { - h.program.snapshot.latestChangedDtsFile = latestChangedDtsFile - h.program.snapshot.buildInfoEmitPending.Store(true) - h.program.snapshot.hasChangedDtsFile = true - } - if update, ok := h.emitUpdates.Load(file.Path()); ok { - if !update.dtsErrorsFromCache { - if update.pendingKind == 0 { - h.program.snapshot.affectedFilesPendingEmit.Delete(file.Path()) - } else { - h.program.snapshot.affectedFilesPendingEmit.Store(file.Path(), update.pendingKind) - } - h.program.snapshot.buildInfoEmitPending.Store(true) - } - if update.result != nil { - results = append(results, update.result) - if len(update.result.Diagnostics) != 0 { - h.program.snapshot.emitDiagnosticsPerFile.Store(file.Path(), &diagnosticsOrBuildInfoDiagnosticsWithFileName{diagnostics: update.result.Diagnostics}) - } - } - } - } - return results - } else if h.hasEmitDiagnostics.Load() { - h.program.snapshot.hasEmitDiagnostics = true - } - return nil -} - -func emitFiles(ctx context.Context, program *Program, options compiler.EmitOptions, isForDtsErrors bool) *compiler.EmitResult { - emitHandler := &emitFilesHandler{ctx: ctx, program: program, isForDtsErrors: isForDtsErrors} - - // Single file emit - do direct from program - if !isForDtsErrors && options.TargetSourceFile != nil { - result := program.program.Emit(ctx, emitHandler.getEmitOptions(options)) - if ctx.Err() != nil { - return nil - } - emitHandler.updateSnapshot() - return result - } - - // Emit only affected files if using builder for emit - return emitHandler.emitAllAffectedFiles(options) -} diff --git a/kitcom/internal/tsgo/execute/incremental/host.go b/kitcom/internal/tsgo/execute/incremental/host.go deleted file mode 100644 index 6191275..0000000 --- a/kitcom/internal/tsgo/execute/incremental/host.go +++ /dev/null @@ -1,39 +0,0 @@ -package incremental - -import ( - "time" - - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/compiler" -) - -type Host interface { - GetMTime(fileName string) time.Time - SetMTime(fileName string, mTime time.Time) error -} - -type host struct { - host compiler.CompilerHost -} - -var _ Host = (*host)(nil) - -func (b *host) GetMTime(fileName string) time.Time { - return GetMTime(b.host, fileName) -} - -func (b *host) SetMTime(fileName string, mTime time.Time) error { - return b.host.FS().Chtimes(fileName, time.Time{}, mTime) -} - -func CreateHost(compilerHost compiler.CompilerHost) Host { - return &host{host: compilerHost} -} - -func GetMTime(host compiler.CompilerHost, fileName string) time.Time { - stat := host.FS().Stat(fileName) - var mTime time.Time - if stat != nil { - mTime = stat.ModTime() - } - return mTime -} diff --git a/kitcom/internal/tsgo/execute/incremental/incremental.go b/kitcom/internal/tsgo/execute/incremental/incremental.go deleted file mode 100644 index a0c4ce0..0000000 --- a/kitcom/internal/tsgo/execute/incremental/incremental.go +++ /dev/null @@ -1,56 +0,0 @@ -package incremental - -import ( - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/compiler" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tsoptions" - "github.com/go-json-experiment/json" -) - -type BuildInfoReader interface { - ReadBuildInfo(config *tsoptions.ParsedCommandLine) *BuildInfo -} - -var _ BuildInfoReader = (*buildInfoReader)(nil) - -type buildInfoReader struct { - host compiler.CompilerHost -} - -func (r *buildInfoReader) ReadBuildInfo(config *tsoptions.ParsedCommandLine) *BuildInfo { - buildInfoFileName := config.GetBuildInfoFileName() - if buildInfoFileName == "" { - return nil - } - - // Read build info file - data, ok := r.host.FS().ReadFile(buildInfoFileName) - if !ok { - return nil - } - var buildInfo BuildInfo - err := json.Unmarshal([]byte(data), &buildInfo) - if err != nil { - return nil - } - return &buildInfo -} - -func NewBuildInfoReader( - host compiler.CompilerHost, -) BuildInfoReader { - return &buildInfoReader{host: host} -} - -func ReadBuildInfoProgram(config *tsoptions.ParsedCommandLine, reader BuildInfoReader, host compiler.CompilerHost) *Program { - // Read buildInfo file - buildInfo := reader.ReadBuildInfo(config) - if buildInfo == nil || !buildInfo.IsValidVersion() || !buildInfo.IsIncremental() { - return nil - } - - // Convert to information that can be used to create incremental program - incrementalProgram := &Program{ - snapshot: buildInfoToSnapshot(buildInfo, config, host), - } - return incrementalProgram -} diff --git a/kitcom/internal/tsgo/execute/incremental/program.go b/kitcom/internal/tsgo/execute/incremental/program.go deleted file mode 100644 index fc3a03d..0000000 --- a/kitcom/internal/tsgo/execute/incremental/program.go +++ /dev/null @@ -1,364 +0,0 @@ -package incremental - -import ( - "context" - "fmt" - "slices" - - "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/diagnostics" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/outputpaths" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath" - "github.com/go-json-experiment/json" -) - -type SignatureUpdateKind byte - -const ( - SignatureUpdateKindComputedDts SignatureUpdateKind = iota - SignatureUpdateKindStoredAtEmit - SignatureUpdateKindUsedVersion -) - -type Program struct { - snapshot *snapshot - program *compiler.Program - host Host - - // Testing data - testingData *TestingData -} - -var _ compiler.ProgramLike = (*Program)(nil) - -func NewProgram(program *compiler.Program, oldProgram *Program, host Host, testing bool) *Program { - incrementalProgram := &Program{ - snapshot: programToSnapshot(program, oldProgram, testing), - program: program, - host: host, - } - - if testing { - incrementalProgram.testingData = &TestingData{} - incrementalProgram.testingData.SemanticDiagnosticsPerFile = &incrementalProgram.snapshot.semanticDiagnosticsPerFile - if oldProgram != nil { - incrementalProgram.testingData.OldProgramSemanticDiagnosticsPerFile = &oldProgram.snapshot.semanticDiagnosticsPerFile - } else { - incrementalProgram.testingData.OldProgramSemanticDiagnosticsPerFile = &collections.SyncMap[tspath.Path, *diagnosticsOrBuildInfoDiagnosticsWithFileName]{} - } - incrementalProgram.testingData.UpdatedSignatureKinds = make(map[tspath.Path]SignatureUpdateKind) - } - return incrementalProgram -} - -type TestingData struct { - SemanticDiagnosticsPerFile *collections.SyncMap[tspath.Path, *diagnosticsOrBuildInfoDiagnosticsWithFileName] - OldProgramSemanticDiagnosticsPerFile *collections.SyncMap[tspath.Path, *diagnosticsOrBuildInfoDiagnosticsWithFileName] - UpdatedSignatureKinds map[tspath.Path]SignatureUpdateKind -} - -func (p *Program) GetTestingData() *TestingData { - return p.testingData -} - -func (p *Program) panicIfNoProgram(method string) { - if p.program == nil { - panic(method + ": should not be called without program") - } -} - -func (p *Program) GetProgram() *compiler.Program { - p.panicIfNoProgram("GetProgram") - return p.program -} - -func (p *Program) HasChangedDtsFile() bool { - return p.snapshot.hasChangedDtsFile -} - -// Options implements compiler.AnyProgram interface. -func (p *Program) Options() *core.CompilerOptions { - return p.snapshot.options -} - -// GetSourceFiles implements compiler.AnyProgram interface. -func (p *Program) GetSourceFiles() []*ast.SourceFile { - p.panicIfNoProgram("GetSourceFiles") - return p.program.GetSourceFiles() -} - -// GetConfigFileParsingDiagnostics implements compiler.AnyProgram interface. -func (p *Program) GetConfigFileParsingDiagnostics() []*ast.Diagnostic { - p.panicIfNoProgram("GetConfigFileParsingDiagnostics") - return p.program.GetConfigFileParsingDiagnostics() -} - -// GetSyntacticDiagnostics implements compiler.AnyProgram interface. -func (p *Program) GetSyntacticDiagnostics(ctx context.Context, file *ast.SourceFile) []*ast.Diagnostic { - p.panicIfNoProgram("GetSyntacticDiagnostics") - return p.program.GetSyntacticDiagnostics(ctx, file) -} - -// GetBindDiagnostics implements compiler.AnyProgram interface. -func (p *Program) GetBindDiagnostics(ctx context.Context, file *ast.SourceFile) []*ast.Diagnostic { - p.panicIfNoProgram("GetBindDiagnostics") - return p.program.GetBindDiagnostics(ctx, file) -} - -// GetOptionsDiagnostics implements compiler.AnyProgram interface. -func (p *Program) GetOptionsDiagnostics(ctx context.Context) []*ast.Diagnostic { - p.panicIfNoProgram("GetOptionsDiagnostics") - return p.program.GetOptionsDiagnostics(ctx) -} - -func (p *Program) GetProgramDiagnostics() []*ast.Diagnostic { - p.panicIfNoProgram("GetProgramDiagnostics") - return p.program.GetProgramDiagnostics() -} - -func (p *Program) GetGlobalDiagnostics(ctx context.Context) []*ast.Diagnostic { - p.panicIfNoProgram("GetGlobalDiagnostics") - return p.program.GetGlobalDiagnostics(ctx) -} - -// GetSemanticDiagnostics implements compiler.AnyProgram interface. -func (p *Program) GetSemanticDiagnostics(ctx context.Context, file *ast.SourceFile) []*ast.Diagnostic { - p.panicIfNoProgram("GetSemanticDiagnostics") - if p.snapshot.options.NoCheck.IsTrue() { - return nil - } - - // Ensure all the diagnsotics are cached - p.collectSemanticDiagnosticsOfAffectedFiles(ctx, file) - if ctx.Err() != nil { - return nil - } - - // Return result from cache - if file != nil { - return p.getSemanticDiagnosticsOfFile(file) - } - - var diagnostics []*ast.Diagnostic - for _, file := range p.program.GetSourceFiles() { - diagnostics = append(diagnostics, p.getSemanticDiagnosticsOfFile(file)...) - } - return diagnostics -} - -func (p *Program) getSemanticDiagnosticsOfFile(file *ast.SourceFile) []*ast.Diagnostic { - cachedDiagnostics, ok := p.snapshot.semanticDiagnosticsPerFile.Load(file.Path()) - if !ok { - panic("After handling all the affected files, there shouldnt be more changes") - } - return slices.Concat( - compiler.FilterNoEmitSemanticDiagnostics(cachedDiagnostics.getDiagnostics(p.program, file), p.snapshot.options), - p.program.GetIncludeProcessorDiagnostics(file), - ) -} - -// GetDeclarationDiagnostics implements compiler.AnyProgram interface. -func (p *Program) GetDeclarationDiagnostics(ctx context.Context, file *ast.SourceFile) []*ast.Diagnostic { - p.panicIfNoProgram("GetDeclarationDiagnostics") - result := emitFiles(ctx, p, compiler.EmitOptions{ - TargetSourceFile: file, - }, true) - if result != nil { - return result.Diagnostics - } - return nil -} - -// GetModeForUsageLocation implements compiler.AnyProgram interface. -func (p *Program) Emit(ctx context.Context, options compiler.EmitOptions) *compiler.EmitResult { - p.panicIfNoProgram("Emit") - - var result *compiler.EmitResult - if p.snapshot.options.NoEmit.IsTrue() { - result = &compiler.EmitResult{EmitSkipped: true} - } else { - result = compiler.HandleNoEmitOnError(ctx, p, options.TargetSourceFile) - if ctx.Err() != nil { - return nil - } - } - if result != nil { - if options.TargetSourceFile != nil { - return result - } - - // Emit buildInfo and combine result - buildInfoResult := p.emitBuildInfo(ctx, options) - if buildInfoResult != nil { - result.Diagnostics = append(result.Diagnostics, buildInfoResult.Diagnostics...) - result.EmittedFiles = append(result.EmittedFiles, buildInfoResult.EmittedFiles...) - } - return result - } - return emitFiles(ctx, p, options, false) -} - -// Handle affected files and cache the semantic diagnostics for all of them or the file asked for -func (p *Program) collectSemanticDiagnosticsOfAffectedFiles(ctx context.Context, file *ast.SourceFile) { - if p.snapshot.canUseIncrementalState() { - // Get all affected files - collectAllAffectedFiles(ctx, p) - if ctx.Err() != nil { - return - } - - if p.snapshot.semanticDiagnosticsPerFile.Size() == len(p.program.GetSourceFiles()) { - // If we have all the files, - return - } - } - - var affectedFiles []*ast.SourceFile - if file != nil { - _, ok := p.snapshot.semanticDiagnosticsPerFile.Load(file.Path()) - if ok { - return - } - affectedFiles = []*ast.SourceFile{file} - } else { - for _, file := range p.program.GetSourceFiles() { - if _, ok := p.snapshot.semanticDiagnosticsPerFile.Load(file.Path()); !ok { - affectedFiles = append(affectedFiles, file) - } - } - } - - // Get their diagnostics and cache them - diagnosticsPerFile := p.program.GetSemanticDiagnosticsNoFilter(ctx, affectedFiles) - // commit changes if no err - if ctx.Err() != nil { - return - } - - // Commit changes to snapshot - for file, diagnostics := range diagnosticsPerFile { - p.snapshot.semanticDiagnosticsPerFile.Store(file.Path(), &diagnosticsOrBuildInfoDiagnosticsWithFileName{diagnostics: diagnostics}) - } - if p.snapshot.semanticDiagnosticsPerFile.Size() == len(p.program.GetSourceFiles()) && p.snapshot.checkPending && !p.snapshot.options.NoCheck.IsTrue() { - p.snapshot.checkPending = false - } - p.snapshot.buildInfoEmitPending.Store(true) -} - -func (p *Program) emitBuildInfo(ctx context.Context, options compiler.EmitOptions) *compiler.EmitResult { - buildInfoFileName := outputpaths.GetBuildInfoFileName(p.snapshot.options, tspath.ComparePathsOptions{ - CurrentDirectory: p.program.GetCurrentDirectory(), - UseCaseSensitiveFileNames: p.program.UseCaseSensitiveFileNames(), - }) - if buildInfoFileName == "" || p.program.IsEmitBlocked(buildInfoFileName) { - return nil - } - if p.snapshot.hasErrors == core.TSUnknown { - p.ensureHasErrorsForState(ctx, p.program) - if p.snapshot.hasErrors != p.snapshot.hasErrorsFromOldState || p.snapshot.hasSemanticErrors != p.snapshot.hasSemanticErrorsFromOldState { - p.snapshot.buildInfoEmitPending.Store(true) - } - } - if !p.snapshot.buildInfoEmitPending.Load() { - return nil - } - if ctx.Err() != nil { - return nil - } - buildInfo := snapshotToBuildInfo(p.snapshot, p.program, buildInfoFileName) - text, err := json.Marshal(buildInfo) - if err != nil { - panic(fmt.Sprintf("Failed to marshal build info: %v", err)) - } - if options.WriteFile != nil { - err = options.WriteFile(buildInfoFileName, string(text), false, &compiler.WriteFileData{ - BuildInfo: buildInfo, - }) - } else { - err = p.program.Host().FS().WriteFile(buildInfoFileName, string(text), false) - } - if err != nil { - return &compiler.EmitResult{ - EmitSkipped: true, - Diagnostics: []*ast.Diagnostic{ - ast.NewCompilerDiagnostic(diagnostics.Could_not_write_file_0_Colon_1, buildInfoFileName, err.Error()), - }, - } - } - p.snapshot.buildInfoEmitPending.Store(false) - return &compiler.EmitResult{ - EmitSkipped: false, - EmittedFiles: []string{buildInfoFileName}, - } -} - -func (p *Program) ensureHasErrorsForState(ctx context.Context, program *compiler.Program) { - var hasIncludeProcessingDiagnostics func() bool - var hasEmitDiagnostics bool - if p.snapshot.canUseIncrementalState() { - if slices.ContainsFunc(program.GetSourceFiles(), func(file *ast.SourceFile) bool { - if _, ok := p.snapshot.emitDiagnosticsPerFile.Load(file.Path()); ok { - // emit diagnostics will be encoded in buildInfo; - return true - } - if hasIncludeProcessingDiagnostics == nil && len(p.program.GetIncludeProcessorDiagnostics(file)) > 0 { - hasIncludeProcessingDiagnostics = func() bool { return true } - } - return false - }) { - hasEmitDiagnostics = true - } - if hasIncludeProcessingDiagnostics == nil { - hasIncludeProcessingDiagnostics = func() bool { return false } - } - } else { - hasEmitDiagnostics = p.snapshot.hasEmitDiagnostics - hasIncludeProcessingDiagnostics = func() bool { - return slices.ContainsFunc(program.GetSourceFiles(), func(file *ast.SourceFile) bool { - return len(p.program.GetIncludeProcessorDiagnostics(file)) > 0 - }) - } - } - - if hasEmitDiagnostics { - // Record this for only non incremental build info - p.snapshot.hasErrors = core.IfElse(p.snapshot.options.IsIncremental(), core.TSFalse, core.TSTrue) - // Dont need to encode semantic errors state since the emit diagnostics are encoded - p.snapshot.hasSemanticErrors = false - return - } - - if hasIncludeProcessingDiagnostics() || - len(program.GetConfigFileParsingDiagnostics()) > 0 || - len(program.GetSyntacticDiagnostics(ctx, nil)) > 0 || - len(program.GetProgramDiagnostics()) > 0 || - len(program.GetOptionsDiagnostics(ctx)) > 0 || - len(program.GetGlobalDiagnostics(ctx)) > 0 { - p.snapshot.hasErrors = core.TSTrue - // Dont need to encode semantic errors state since the syntax and program diagnostics are encoded as present - p.snapshot.hasSemanticErrors = false - return - } - - p.snapshot.hasErrors = core.TSFalse - // Check semantic and emit diagnostics first as we dont need to ask program about it - if slices.ContainsFunc(program.GetSourceFiles(), func(file *ast.SourceFile) bool { - semanticDiagnostics, ok := p.snapshot.semanticDiagnosticsPerFile.Load(file.Path()) - if !ok { - // Missing semantic diagnostics in cache will be encoded in incremental buildInfo - return p.snapshot.options.IsIncremental() - } - if len(semanticDiagnostics.diagnostics) > 0 || len(semanticDiagnostics.buildInfoDiagnostics) > 0 { - // cached semantic diagnostics will be encoded in buildInfo - return true - } - return false - }) { - // Because semantic diagnostics are recorded in buildInfo, we dont need to encode hasErrors in incremental buildInfo - // But encode as errors in non incremental buildInfo - p.snapshot.hasSemanticErrors = !p.snapshot.options.IsIncremental() - } -} diff --git a/kitcom/internal/tsgo/execute/incremental/programtosnapshot.go b/kitcom/internal/tsgo/execute/incremental/programtosnapshot.go deleted file mode 100644 index 6b21aa5..0000000 --- a/kitcom/internal/tsgo/execute/incremental/programtosnapshot.go +++ /dev/null @@ -1,300 +0,0 @@ -package incremental - -import ( - "context" - - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/checker" - "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 programToSnapshot(program *compiler.Program, oldProgram *Program, hashWithText bool) *snapshot { - if oldProgram != nil && oldProgram.program == program { - return oldProgram.snapshot - } - snapshot := &snapshot{ - options: program.Options(), - hashWithText: hashWithText, - checkPending: program.Options().NoCheck.IsTrue(), - } - to := &toProgramSnapshot{ - program: program, - oldProgram: oldProgram, - snapshot: snapshot, - } - - if to.snapshot.canUseIncrementalState() { - to.reuseFromOldProgram() - to.computeProgramFileChanges() - to.handleFileDelete() - to.handlePendingEmit() - to.handlePendingCheck() - } - return snapshot -} - -type toProgramSnapshot struct { - program *compiler.Program - oldProgram *Program - snapshot *snapshot - globalFileRemoved bool -} - -func (t *toProgramSnapshot) reuseFromOldProgram() { - if t.oldProgram != nil { - if t.snapshot.options.Composite.IsTrue() { - t.snapshot.latestChangedDtsFile = t.oldProgram.snapshot.latestChangedDtsFile - } - // Copy old snapshot's changed files set - t.oldProgram.snapshot.changedFilesSet.Range(func(key tspath.Path) bool { - t.snapshot.changedFilesSet.Add(key) - return true - }) - t.oldProgram.snapshot.affectedFilesPendingEmit.Range(func(key tspath.Path, emitKind FileEmitKind) bool { - t.snapshot.affectedFilesPendingEmit.Store(key, emitKind) - return true - }) - t.snapshot.buildInfoEmitPending.Store(t.oldProgram.snapshot.buildInfoEmitPending.Load()) - t.snapshot.hasErrorsFromOldState = t.oldProgram.snapshot.hasErrors - t.snapshot.hasSemanticErrorsFromOldState = t.oldProgram.snapshot.hasSemanticErrors - } else { - t.snapshot.buildInfoEmitPending.Store(t.snapshot.options.IsIncremental()) - } -} - -func (t *toProgramSnapshot) computeProgramFileChanges() { - canCopySemanticDiagnostics := t.oldProgram != nil && - !tsoptions.CompilerOptionsAffectSemanticDiagnostics(t.oldProgram.snapshot.options, t.program.Options()) - // We can only reuse emit signatures (i.e. .d.ts signatures) if the .d.ts file is unchanged, - // which will eg be depedent on change in options like declarationDir and outDir options are unchanged. - // We need to look in oldState.compilerOptions, rather than oldCompilerOptions (i.e.we need to disregard useOldState) because - // oldCompilerOptions can be undefined if there was change in say module from None to some other option - // which would make useOldState as false since we can now use reference maps that are needed to track what to emit, what to check etc - // but that option change does not affect d.ts file name so emitSignatures should still be reused. - canCopyEmitSignatures := t.snapshot.options.Composite.IsTrue() && - t.oldProgram != nil && - !tsoptions.CompilerOptionsAffectDeclarationPath(t.oldProgram.snapshot.options, t.program.Options()) - copyDeclarationFileDiagnostics := canCopySemanticDiagnostics && - t.snapshot.options.SkipLibCheck.IsTrue() == t.oldProgram.snapshot.options.SkipLibCheck.IsTrue() - copyLibFileDiagnostics := copyDeclarationFileDiagnostics && - t.snapshot.options.SkipDefaultLibCheck.IsTrue() == t.oldProgram.snapshot.options.SkipDefaultLibCheck.IsTrue() - - files := t.program.GetSourceFiles() - wg := core.NewWorkGroup(t.program.SingleThreaded()) - for _, file := range files { - wg.Queue(func() { - version := t.snapshot.computeHash(file.Text()) - impliedNodeFormat := t.program.GetSourceFileMetaData(file.Path()).ImpliedNodeFormat - affectsGlobalScope := fileAffectsGlobalScope(file) - var signature string - newReferences := getReferencedFiles(t.program, file) - if newReferences != nil { - t.snapshot.referencedMap.storeReferences(file.Path(), newReferences) - } - if t.oldProgram != nil { - if oldFileInfo, ok := t.oldProgram.snapshot.fileInfos.Load(file.Path()); ok { - signature = oldFileInfo.signature - if oldFileInfo.version != version || oldFileInfo.affectsGlobalScope != affectsGlobalScope || oldFileInfo.impliedNodeFormat != impliedNodeFormat { - t.snapshot.addFileToChangeSet(file.Path()) - } else if oldReferences, _ := t.oldProgram.snapshot.referencedMap.getReferences(file.Path()); !newReferences.Equals(oldReferences) { - // Referenced files changed - t.snapshot.addFileToChangeSet(file.Path()) - } else if newReferences != nil { - for refPath := range newReferences.Keys() { - if t.program.GetSourceFileByPath(refPath) == nil { - if _, ok := t.oldProgram.snapshot.fileInfos.Load(refPath); ok { - // Referenced file was deleted in the new program - t.snapshot.addFileToChangeSet(file.Path()) - break - } - } - } - } - } else { - t.snapshot.addFileToChangeSet(file.Path()) - } - if !t.snapshot.changedFilesSet.Has(file.Path()) { - if emitDiagnostics, ok := t.oldProgram.snapshot.emitDiagnosticsPerFile.Load(file.Path()); ok { - t.snapshot.emitDiagnosticsPerFile.Store(file.Path(), emitDiagnostics) - } - if canCopySemanticDiagnostics { - if (!file.IsDeclarationFile || copyDeclarationFileDiagnostics) && - (!t.program.IsSourceFileDefaultLibrary(file.Path()) || copyLibFileDiagnostics) { - // Unchanged file copy diagnostics - if diagnostics, ok := t.oldProgram.snapshot.semanticDiagnosticsPerFile.Load(file.Path()); ok { - t.snapshot.semanticDiagnosticsPerFile.Store(file.Path(), diagnostics) - } - } - } - } - if canCopyEmitSignatures { - if oldEmitSignature, ok := t.oldProgram.snapshot.emitSignatures.Load(file.Path()); ok { - t.snapshot.emitSignatures.Store(file.Path(), oldEmitSignature.getNewEmitSignature(t.oldProgram.snapshot.options, t.snapshot.options)) - } - } - } else { - t.snapshot.addFileToAffectedFilesPendingEmit(file.Path(), GetFileEmitKind(t.snapshot.options)) - signature = version - } - t.snapshot.fileInfos.Store(file.Path(), &fileInfo{ - version: version, - signature: signature, - affectsGlobalScope: affectsGlobalScope, - impliedNodeFormat: impliedNodeFormat, - }) - }) - } - wg.RunAndWait() -} - -func (t *toProgramSnapshot) handleFileDelete() { - if t.oldProgram != nil { - // If the global file is removed, add all files as changed - t.oldProgram.snapshot.fileInfos.Range(func(filePath tspath.Path, oldInfo *fileInfo) bool { - if _, ok := t.snapshot.fileInfos.Load(filePath); !ok { - if oldInfo.affectsGlobalScope { - for _, file := range t.snapshot.getAllFilesExcludingDefaultLibraryFile(t.program, nil) { - t.snapshot.addFileToChangeSet(file.Path()) - } - t.globalFileRemoved = true - } else { - t.snapshot.buildInfoEmitPending.Store(true) - } - return false - } - return true - }) - } -} - -func (t *toProgramSnapshot) handlePendingEmit() { - if t.oldProgram != nil && !t.globalFileRemoved { - // If options affect emit, then we need to do complete emit per compiler options - // otherwise only the js or dts that needs to emitted because its different from previously emitted options - var pendingEmitKind FileEmitKind - if tsoptions.CompilerOptionsAffectEmit(t.oldProgram.snapshot.options, t.snapshot.options) { - pendingEmitKind = GetFileEmitKind(t.snapshot.options) - } else { - pendingEmitKind = getPendingEmitKindWithOptions(t.snapshot.options, t.oldProgram.snapshot.options) - } - if pendingEmitKind != FileEmitKindNone { - // Add all files to affectedFilesPendingEmit since emit changed - for _, file := range t.program.GetSourceFiles() { - // Add to affectedFilesPending emit only if not changed since any changed file will do full emit - if !t.snapshot.changedFilesSet.Has(file.Path()) { - t.snapshot.addFileToAffectedFilesPendingEmit(file.Path(), pendingEmitKind) - } - } - t.snapshot.buildInfoEmitPending.Store(true) - } - } -} - -func (t *toProgramSnapshot) handlePendingCheck() { - if t.oldProgram != nil && - t.snapshot.semanticDiagnosticsPerFile.Size() != len(t.program.GetSourceFiles()) && - t.oldProgram.snapshot.checkPending != t.snapshot.checkPending { - t.snapshot.buildInfoEmitPending.Store(true) - } -} - -func fileAffectsGlobalScope(file *ast.SourceFile) bool { - // if file contains anything that augments to global scope we need to build them as if - // they are global files as well as module - if core.Some(file.ModuleAugmentations, func(augmentation *ast.ModuleName) bool { - return ast.IsGlobalScopeAugmentation(augmentation.Parent) - }) { - return true - } - - if ast.IsExternalOrCommonJSModule(file) || ast.IsJsonSourceFile(file) { - return false - } - - // For script files that contains only ambient external modules, although they are not actually external module files, - // they can only be consumed via importing elements from them. Regular script files cannot consume them. Therefore, - // there are no point to rebuild all script files if these special files have changed. However, if any statement - // in the file is not ambient external module, we treat it as a regular script file. - return file.Statements != nil && - file.Statements.Nodes != nil && - core.Some(file.Statements.Nodes, func(stmt *ast.Node) bool { - return !ast.IsModuleWithStringLiteralName(stmt) - }) -} - -func addReferencedFilesFromSymbol(file *ast.SourceFile, referencedFiles *collections.Set[tspath.Path], symbol *ast.Symbol) { - if symbol == nil { - return - } - for _, declaration := range symbol.Declarations { - fileOfDecl := ast.GetSourceFileOfNode(declaration) - if fileOfDecl == nil { - continue - } - if file != fileOfDecl { - referencedFiles.Add(fileOfDecl.Path()) - } - } -} - -// Get the module source file and all augmenting files from the import name node from file -func addReferencedFilesFromImportLiteral(file *ast.SourceFile, referencedFiles *collections.Set[tspath.Path], checker *checker.Checker, importName *ast.LiteralLikeNode) { - symbol := checker.GetSymbolAtLocation(importName) - addReferencedFilesFromSymbol(file, referencedFiles, symbol) -} - -// Gets the path to reference file from file name, it could be resolvedPath if present otherwise path -func addReferencedFileFromFileName(program *compiler.Program, fileName string, referencedFiles *collections.Set[tspath.Path], sourceFileDirectory string) { - if redirect := program.GetParseFileRedirect(fileName); redirect != "" { - referencedFiles.Add(tspath.ToPath(redirect, program.GetCurrentDirectory(), program.UseCaseSensitiveFileNames())) - } else { - referencedFiles.Add(tspath.ToPath(fileName, sourceFileDirectory, program.UseCaseSensitiveFileNames())) - } -} - -// Gets the referenced files for a file from the program with values for the keys as referenced file's path to be true -func getReferencedFiles(program *compiler.Program, file *ast.SourceFile) *collections.Set[tspath.Path] { - referencedFiles := collections.Set[tspath.Path]{} - - // We need to use a set here since the code can contain the same import twice, - // but that will only be one dependency. - // To avoid invernal conversion, the key of the referencedFiles map must be of type Path - checker, done := program.GetTypeCheckerForFile(context.TODO(), file) - defer done() - for _, importName := range file.Imports() { - addReferencedFilesFromImportLiteral(file, &referencedFiles, checker, importName) - } - - sourceFileDirectory := tspath.GetDirectoryPath(file.FileName()) - // Handle triple slash references - for _, referencedFile := range file.ReferencedFiles { - addReferencedFileFromFileName(program, referencedFile.FileName, &referencedFiles, sourceFileDirectory) - } - - // Handle type reference directives - if typeRefsInFile, ok := program.GetResolvedTypeReferenceDirectives()[file.Path()]; ok { - for _, typeRef := range typeRefsInFile { - if typeRef.ResolvedFileName != "" { - addReferencedFileFromFileName(program, typeRef.ResolvedFileName, &referencedFiles, sourceFileDirectory) - } - } - } - - // Add module augmentation as references - for _, moduleName := range file.ModuleAugmentations { - if !ast.IsStringLiteral(moduleName) { - continue - } - addReferencedFilesFromImportLiteral(file, &referencedFiles, checker, moduleName) - } - - // From ambient modules - for _, ambientModule := range checker.GetAmbientModules() { - addReferencedFilesFromSymbol(file, &referencedFiles, ambientModule) - } - return core.IfElse(referencedFiles.Len() > 0, &referencedFiles, nil) -} diff --git a/kitcom/internal/tsgo/execute/incremental/referencemap.go b/kitcom/internal/tsgo/execute/incremental/referencemap.go deleted file mode 100644 index d50fb1c..0000000 --- a/kitcom/internal/tsgo/execute/incremental/referencemap.go +++ /dev/null @@ -1,52 +0,0 @@ -package incremental - -import ( - "iter" - "maps" - "slices" - "sync" - - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/collections" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath" -) - -type referenceMap struct { - references collections.SyncMap[tspath.Path, *collections.Set[tspath.Path]] - referencedBy map[tspath.Path]*collections.Set[tspath.Path] - referenceBy sync.Once -} - -func (r *referenceMap) storeReferences(path tspath.Path, refs *collections.Set[tspath.Path]) { - r.references.Store(path, refs) -} - -func (r *referenceMap) getReferences(path tspath.Path) (*collections.Set[tspath.Path], bool) { - refs, ok := r.references.Load(path) - return refs, ok -} - -func (r *referenceMap) getPathsWithReferences() []tspath.Path { - return slices.Collect(r.references.Keys()) -} - -func (r *referenceMap) getReferencedBy(path tspath.Path) iter.Seq[tspath.Path] { - r.referenceBy.Do(func() { - r.referencedBy = make(map[tspath.Path]*collections.Set[tspath.Path]) - r.references.Range(func(key tspath.Path, value *collections.Set[tspath.Path]) bool { - for ref := range value.Keys() { - set, ok := r.referencedBy[ref] - if !ok { - set = &collections.Set[tspath.Path]{} - r.referencedBy[ref] = set - } - set.Add(key) - } - return true - }) - }) - refs, ok := r.referencedBy[path] - if ok { - return maps.Keys(refs.Keys()) - } - return func(yield func(tspath.Path) bool) {} -} diff --git a/kitcom/internal/tsgo/execute/incremental/snapshot.go b/kitcom/internal/tsgo/execute/incremental/snapshot.go deleted file mode 100644 index 4621d3e..0000000 --- a/kitcom/internal/tsgo/execute/incremental/snapshot.go +++ /dev/null @@ -1,323 +0,0 @@ -package incremental - -import ( - "encoding/hex" - "fmt" - "strings" - "sync" - "sync/atomic" - - "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/diagnostics" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath" - "github.com/zeebo/xxh3" -) - -type fileInfo struct { - version string - signature string - affectsGlobalScope bool - impliedNodeFormat core.ResolutionMode -} - -func (f *fileInfo) Version() string { return f.version } -func (f *fileInfo) Signature() string { return f.signature } -func (f *fileInfo) AffectsGlobalScope() bool { return f.affectsGlobalScope } -func (f *fileInfo) ImpliedNodeFormat() core.ResolutionMode { return f.impliedNodeFormat } - -func ComputeHash(text string, hashWithText bool) string { - hashBytes := xxh3.Hash128([]byte(text)).Bytes() - hash := hex.EncodeToString(hashBytes[:]) - if hashWithText { - hash += "-" + text - } - return hash -} - -type FileEmitKind uint32 - -const ( - FileEmitKindNone FileEmitKind = 0 - FileEmitKindJs FileEmitKind = 1 << 0 // emit js file - FileEmitKindJsMap FileEmitKind = 1 << 1 // emit js.map file - FileEmitKindJsInlineMap FileEmitKind = 1 << 2 // emit inline source map in js file - FileEmitKindDtsErrors FileEmitKind = 1 << 3 // emit dts errors - FileEmitKindDtsEmit FileEmitKind = 1 << 4 // emit d.ts file - FileEmitKindDtsMap FileEmitKind = 1 << 5 // emit d.ts.map file - - FileEmitKindDts = FileEmitKindDtsErrors | FileEmitKindDtsEmit - FileEmitKindAllJs = FileEmitKindJs | FileEmitKindJsMap | FileEmitKindJsInlineMap - FileEmitKindAllDtsEmit = FileEmitKindDtsEmit | FileEmitKindDtsMap - FileEmitKindAllDts = FileEmitKindDts | FileEmitKindDtsMap - FileEmitKindAll = FileEmitKindAllJs | FileEmitKindAllDts -) - -func GetFileEmitKind(options *core.CompilerOptions) FileEmitKind { - result := FileEmitKindJs - if options.SourceMap.IsTrue() { - result |= FileEmitKindJsMap - } - if options.InlineSourceMap.IsTrue() { - result |= FileEmitKindJsInlineMap - } - if options.GetEmitDeclarations() { - result |= FileEmitKindDts - } - if options.DeclarationMap.IsTrue() { - result |= FileEmitKindDtsMap - } - if options.EmitDeclarationOnly.IsTrue() { - result &= FileEmitKindAllDts - } - return result -} - -func getPendingEmitKindWithOptions(options *core.CompilerOptions, oldOptions *core.CompilerOptions) FileEmitKind { - oldEmitKind := GetFileEmitKind(oldOptions) - newEmitKind := GetFileEmitKind(options) - return getPendingEmitKind(newEmitKind, oldEmitKind) -} - -func getPendingEmitKind(emitKind FileEmitKind, oldEmitKind FileEmitKind) FileEmitKind { - if oldEmitKind == emitKind { - return FileEmitKindNone - } - if oldEmitKind == 0 || emitKind == 0 { - return emitKind - } - diff := oldEmitKind ^ emitKind - result := FileEmitKindNone - // If there is diff in Js emit, pending emit is js emit flags - if (diff & FileEmitKindAllJs) != 0 { - result |= emitKind & FileEmitKindAllJs - } - // If dts errors pending, add dts errors flag - if (diff & FileEmitKindDtsErrors) != 0 { - result |= emitKind & FileEmitKindAllDts - } - // If there is diff in Dts emit, pending emit is dts emit flags - if (diff & FileEmitKindAllDtsEmit) != 0 { - result |= emitKind & FileEmitKindAllDtsEmit - } - return result -} - -// Signature (Hash of d.ts emitted), is string if it was emitted using same d.ts.map option as what compilerOptions indicate, -// otherwise tuple of string -type emitSignature struct { - signature string - signatureWithDifferentOptions []string -} - -// Covert to Emit signature based on oldOptions and EmitSignature format -// If d.ts map options differ then swap the format, otherwise use as is -func (e *emitSignature) getNewEmitSignature(oldOptions *core.CompilerOptions, newOptions *core.CompilerOptions) *emitSignature { - if oldOptions.DeclarationMap.IsTrue() == newOptions.DeclarationMap.IsTrue() { - return e - } - if e.signatureWithDifferentOptions == nil { - return &emitSignature{ - signatureWithDifferentOptions: []string{e.signature}, - } - } else { - return &emitSignature{ - signature: e.signatureWithDifferentOptions[0], - } - } -} - -type buildInfoDiagnosticWithFileName struct { - // filename if it is for a File thats other than its stored for - file tspath.Path - noFile bool - pos int - end int - code int32 - category diagnostics.Category - message string - messageChain []*buildInfoDiagnosticWithFileName - relatedInformation []*buildInfoDiagnosticWithFileName - reportsUnnecessary bool - reportsDeprecated bool - skippedOnNoEmit bool -} - -type diagnosticsOrBuildInfoDiagnosticsWithFileName struct { - diagnostics []*ast.Diagnostic - buildInfoDiagnostics []*buildInfoDiagnosticWithFileName -} - -func (b *buildInfoDiagnosticWithFileName) toDiagnostic(p *compiler.Program, file *ast.SourceFile) *ast.Diagnostic { - var fileForDiagnostic *ast.SourceFile - if b.file != "" { - fileForDiagnostic = p.GetSourceFileByPath(b.file) - } else if !b.noFile { - fileForDiagnostic = file - } - var messageChain []*ast.Diagnostic - for _, msg := range b.messageChain { - messageChain = append(messageChain, msg.toDiagnostic(p, fileForDiagnostic)) - } - var relatedInformation []*ast.Diagnostic - for _, info := range b.relatedInformation { - relatedInformation = append(relatedInformation, info.toDiagnostic(p, fileForDiagnostic)) - } - return ast.NewDiagnosticWith( - fileForDiagnostic, - core.NewTextRange(b.pos, b.end), - b.code, - b.category, - b.message, - messageChain, - relatedInformation, - b.reportsUnnecessary, - b.reportsDeprecated, - b.skippedOnNoEmit, - ) -} - -func (d *diagnosticsOrBuildInfoDiagnosticsWithFileName) getDiagnostics(p *compiler.Program, file *ast.SourceFile) []*ast.Diagnostic { - if d.diagnostics != nil { - return d.diagnostics - } - // Convert and cache the diagnostics - d.diagnostics = core.Map(d.buildInfoDiagnostics, func(diag *buildInfoDiagnosticWithFileName) *ast.Diagnostic { - return diag.toDiagnostic(p, file) - }) - return d.diagnostics -} - -type snapshot struct { - // These are the fields that get serialized - - // Information of the file eg. its version, signature etc - fileInfos collections.SyncMap[tspath.Path, *fileInfo] - options *core.CompilerOptions - // Contains the map of ReferencedSet=Referenced files of the file if module emit is enabled - referencedMap referenceMap - // Cache of semantic diagnostics for files with their Path being the key - semanticDiagnosticsPerFile collections.SyncMap[tspath.Path, *diagnosticsOrBuildInfoDiagnosticsWithFileName] - // Cache of dts emit diagnostics for files with their Path being the key - emitDiagnosticsPerFile collections.SyncMap[tspath.Path, *diagnosticsOrBuildInfoDiagnosticsWithFileName] - // The map has key by source file's path that has been changed - changedFilesSet collections.SyncSet[tspath.Path] - // Files pending to be emitted - affectedFilesPendingEmit collections.SyncMap[tspath.Path, FileEmitKind] - // Name of the file whose dts was the latest to change - latestChangedDtsFile string - // Hash of d.ts emitted for the file, use to track when emit of d.ts changes - emitSignatures collections.SyncMap[tspath.Path, *emitSignature] - // Recorded if program had errors that need to be reported even with --noCheck - hasErrors core.Tristate - // Recorded if program had semantic errors only for non incremental build - hasSemanticErrors bool - // If semantic diagnostic check is pending - checkPending bool - - // Additional fields that are not serialized but needed to track state - - // true if build info emit is pending - buildInfoEmitPending atomic.Bool - hasErrorsFromOldState core.Tristate - hasSemanticErrorsFromOldState bool - allFilesExcludingDefaultLibraryFileOnce sync.Once - // Cache of all files excluding default library file for the current program - allFilesExcludingDefaultLibraryFile []*ast.SourceFile - hasChangedDtsFile bool - hasEmitDiagnostics bool - - // Used with testing to add text of hash for better comparison - hashWithText bool -} - -func (s *snapshot) addFileToChangeSet(filePath tspath.Path) { - s.changedFilesSet.Add(filePath) - s.buildInfoEmitPending.Store(true) -} - -func (s *snapshot) addFileToAffectedFilesPendingEmit(filePath tspath.Path, emitKind FileEmitKind) { - existingKind, _ := s.affectedFilesPendingEmit.Load(filePath) - s.affectedFilesPendingEmit.Store(filePath, existingKind|emitKind) - if emitKind&FileEmitKindDtsErrors != 0 { - s.emitDiagnosticsPerFile.Delete(filePath) - } - s.buildInfoEmitPending.Store(true) -} - -func (s *snapshot) getAllFilesExcludingDefaultLibraryFile(program *compiler.Program, firstSourceFile *ast.SourceFile) []*ast.SourceFile { - s.allFilesExcludingDefaultLibraryFileOnce.Do(func() { - files := program.GetSourceFiles() - s.allFilesExcludingDefaultLibraryFile = make([]*ast.SourceFile, 0, len(files)) - addSourceFile := func(file *ast.SourceFile) { - if !program.IsSourceFileDefaultLibrary(file.Path()) { - s.allFilesExcludingDefaultLibraryFile = append(s.allFilesExcludingDefaultLibraryFile, file) - } - } - if firstSourceFile != nil { - addSourceFile(firstSourceFile) - } - for _, file := range files { - if file != firstSourceFile { - addSourceFile(file) - } - } - }) - return s.allFilesExcludingDefaultLibraryFile -} - -func getTextHandlingSourceMapForSignature(text string, data *compiler.WriteFileData) string { - if data.SourceMapUrlPos != -1 { - return text[:data.SourceMapUrlPos] - } - return text -} - -func (s *snapshot) computeSignatureWithDiagnostics(file *ast.SourceFile, text string, data *compiler.WriteFileData) string { - var builder strings.Builder - builder.WriteString(getTextHandlingSourceMapForSignature(text, data)) - for _, diag := range data.Diagnostics { - diagnosticToStringBuilder(diag, file, &builder) - } - return s.computeHash(builder.String()) -} - -func diagnosticToStringBuilder(diagnostic *ast.Diagnostic, file *ast.SourceFile, builder *strings.Builder) { - if diagnostic == nil { - return - } - builder.WriteString("\n") - if diagnostic.File() != file { - builder.WriteString(tspath.EnsurePathIsNonModuleName(tspath.GetRelativePathFromDirectory( - tspath.GetDirectoryPath(string(file.Path())), - string(diagnostic.File().Path()), - tspath.ComparePathsOptions{}, - ))) - } - if diagnostic.File() != nil { - builder.WriteString(fmt.Sprintf("(%d,%d): ", diagnostic.Pos(), diagnostic.Len())) - } - builder.WriteString(diagnostic.Category().Name()) - builder.WriteString(fmt.Sprintf("%d: ", diagnostic.Code())) - builder.WriteString(diagnostic.Message()) - for _, chain := range diagnostic.MessageChain() { - diagnosticToStringBuilder(chain, file, builder) - } - for _, info := range diagnostic.RelatedInformation() { - diagnosticToStringBuilder(info, file, builder) - } -} - -func (s *snapshot) computeHash(text string) string { - return ComputeHash(text, s.hashWithText) -} - -func (s *snapshot) canUseIncrementalState() bool { - if !s.options.IsIncremental() && s.options.Build.IsTrue() { - // If not incremental build (with tsc -b), we don't need to track state except diagnostics per file so we can use it - return false - } - return true -} diff --git a/kitcom/internal/tsgo/execute/incremental/snapshottobuildinfo.go b/kitcom/internal/tsgo/execute/incremental/snapshottobuildinfo.go deleted file mode 100644 index c4921ed..0000000 --- a/kitcom/internal/tsgo/execute/incremental/snapshottobuildinfo.go +++ /dev/null @@ -1,363 +0,0 @@ -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))), - } - }) -} diff --git a/kitcom/internal/tsgo/execute/tsc.go b/kitcom/internal/tsgo/execute/tsc.go deleted file mode 100644 index cf5dbaa..0000000 --- a/kitcom/internal/tsgo/execute/tsc.go +++ /dev/null @@ -1,319 +0,0 @@ -package execute - -import ( - "context" - "fmt" - "strings" - - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/compiler" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/core" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/diagnostics" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/execute/build" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/execute/incremental" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/execute/tsc" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/format" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/jsonutil" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/parser" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/pprof" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tsoptions" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath" -) - -func CommandLine(sys tsc.System, commandLineArgs []string, testing tsc.CommandLineTesting) tsc.CommandLineResult { - if len(commandLineArgs) > 0 { - // !!! build mode - switch strings.ToLower(commandLineArgs[0]) { - case "-b", "--b", "-build", "--build": - return tscBuildCompilation(sys, tsoptions.ParseBuildCommandLine(commandLineArgs, sys), testing) - // case "-f": - // return fmtMain(sys, commandLineArgs[1], commandLineArgs[1]) - } - } - - return tscCompilation(sys, tsoptions.ParseCommandLine(commandLineArgs, sys), testing) -} - -func fmtMain(sys tsc.System, input, output string) tsc.ExitStatus { - ctx := format.WithFormatCodeSettings(context.Background(), format.GetDefaultFormatCodeSettings("\n"), "\n") - input = string(tspath.ToPath(input, sys.GetCurrentDirectory(), sys.FS().UseCaseSensitiveFileNames())) - output = string(tspath.ToPath(output, sys.GetCurrentDirectory(), sys.FS().UseCaseSensitiveFileNames())) - fileContent, ok := sys.FS().ReadFile(input) - if !ok { - fmt.Fprintln(sys.Writer(), "File not found:", input) - return tsc.ExitStatusNotImplemented - } - text := fileContent - pathified := tspath.ToPath(input, sys.GetCurrentDirectory(), true) - sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{ - FileName: string(pathified), - Path: pathified, - JSDocParsingMode: ast.JSDocParsingModeParseAll, - }, text, core.GetScriptKindFromFileName(string(pathified))) - edits := format.FormatDocument(ctx, sourceFile) - newText := core.ApplyBulkEdits(text, edits) - - if err := sys.FS().WriteFile(output, newText, false); err != nil { - fmt.Fprintln(sys.Writer(), err.Error()) - return tsc.ExitStatusNotImplemented - } - return tsc.ExitStatusSuccess -} - -func tscBuildCompilation(sys tsc.System, buildCommand *tsoptions.ParsedBuildCommandLine, testing tsc.CommandLineTesting) tsc.CommandLineResult { - reportDiagnostic := tsc.CreateDiagnosticReporter(sys, sys.Writer(), buildCommand.CompilerOptions) - - // if (buildOptions.locale) { - // validateLocaleAndSetLanguage(buildOptions.locale, sys, errors); - // } - - if len(buildCommand.Errors) > 0 { - for _, err := range buildCommand.Errors { - reportDiagnostic(err) - } - return tsc.CommandLineResult{Status: tsc.ExitStatusDiagnosticsPresent_OutputsSkipped} - } - - if pprofDir := buildCommand.CompilerOptions.PprofDir; pprofDir != "" { - // !!! stderr? - profileSession := pprof.BeginProfiling(pprofDir, sys.Writer()) - defer profileSession.Stop() - } - - if buildCommand.CompilerOptions.Help.IsTrue() { - tsc.PrintVersion(sys) - tsc.PrintBuildHelp(sys, tsoptions.BuildOpts) - return tsc.CommandLineResult{Status: tsc.ExitStatusSuccess} - } - - orchestrator := build.NewOrchestrator(build.Options{ - Sys: sys, - Command: buildCommand, - Testing: testing, - }) - return orchestrator.Start() -} - -func tscCompilation(sys tsc.System, commandLine *tsoptions.ParsedCommandLine, testing tsc.CommandLineTesting) tsc.CommandLineResult { - configFileName := "" - reportDiagnostic := tsc.CreateDiagnosticReporter(sys, sys.Writer(), commandLine.CompilerOptions()) - // if commandLine.Options().Locale != nil - - if len(commandLine.Errors) > 0 { - for _, e := range commandLine.Errors { - reportDiagnostic(e) - } - return tsc.CommandLineResult{Status: tsc.ExitStatusDiagnosticsPresent_OutputsSkipped} - } - - if pprofDir := commandLine.CompilerOptions().PprofDir; pprofDir != "" { - // !!! stderr? - profileSession := pprof.BeginProfiling(pprofDir, sys.Writer()) - defer profileSession.Stop() - } - - if commandLine.CompilerOptions().Init.IsTrue() { - return tsc.CommandLineResult{Status: tsc.ExitStatusNotImplemented} - } - - if commandLine.CompilerOptions().Version.IsTrue() { - tsc.PrintVersion(sys) - return tsc.CommandLineResult{Status: tsc.ExitStatusSuccess} - } - - if commandLine.CompilerOptions().Help.IsTrue() || commandLine.CompilerOptions().All.IsTrue() { - tsc.PrintHelp(sys, commandLine) - return tsc.CommandLineResult{Status: tsc.ExitStatusSuccess} - } - - if commandLine.CompilerOptions().Watch.IsTrue() && commandLine.CompilerOptions().ListFilesOnly.IsTrue() { - reportDiagnostic(ast.NewCompilerDiagnostic(diagnostics.Options_0_and_1_cannot_be_combined, "watch", "listFilesOnly")) - return tsc.CommandLineResult{Status: tsc.ExitStatusDiagnosticsPresent_OutputsSkipped} - } - - if commandLine.CompilerOptions().Project != "" { - if len(commandLine.FileNames()) != 0 { - reportDiagnostic(ast.NewCompilerDiagnostic(diagnostics.Option_project_cannot_be_mixed_with_source_files_on_a_command_line)) - return tsc.CommandLineResult{Status: tsc.ExitStatusDiagnosticsPresent_OutputsSkipped} - } - - fileOrDirectory := tspath.NormalizePath(commandLine.CompilerOptions().Project) - if sys.FS().DirectoryExists(fileOrDirectory) { - configFileName = tspath.CombinePaths(fileOrDirectory, "tsconfig.json") - if !sys.FS().FileExists(configFileName) { - reportDiagnostic(ast.NewCompilerDiagnostic(diagnostics.Cannot_find_a_tsconfig_json_file_at_the_current_directory_Colon_0, configFileName)) - return tsc.CommandLineResult{Status: tsc.ExitStatusDiagnosticsPresent_OutputsSkipped} - } - } else { - configFileName = fileOrDirectory - if !sys.FS().FileExists(configFileName) { - reportDiagnostic(ast.NewCompilerDiagnostic(diagnostics.The_specified_path_does_not_exist_Colon_0, fileOrDirectory)) - return tsc.CommandLineResult{Status: tsc.ExitStatusDiagnosticsPresent_OutputsSkipped} - } - } - } else if len(commandLine.FileNames()) == 0 { - searchPath := tspath.NormalizePath(sys.GetCurrentDirectory()) - configFileName = findConfigFile(searchPath, sys.FS().FileExists, "tsconfig.json") - } - - if configFileName == "" && len(commandLine.FileNames()) == 0 { - if commandLine.CompilerOptions().ShowConfig.IsTrue() { - reportDiagnostic(ast.NewCompilerDiagnostic(diagnostics.Cannot_find_a_tsconfig_json_file_at_the_current_directory_Colon_0, tspath.NormalizePath(sys.GetCurrentDirectory()))) - } else { - tsc.PrintVersion(sys) - tsc.PrintHelp(sys, commandLine) - } - return tsc.CommandLineResult{Status: tsc.ExitStatusDiagnosticsPresent_OutputsSkipped} - } - - // !!! convert to options with absolute paths is usually done here, but for ease of implementation, it's done in `tsoptions.ParseCommandLine()` - compilerOptionsFromCommandLine := commandLine.CompilerOptions() - configForCompilation := commandLine - extendedConfigCache := &tsc.ExtendedConfigCache{} - var compileTimes tsc.CompileTimes - if configFileName != "" { - configStart := sys.Now() - configParseResult, errors := tsoptions.GetParsedCommandLineOfConfigFile(configFileName, compilerOptionsFromCommandLine, sys, extendedConfigCache) - compileTimes.ConfigTime = sys.Now().Sub(configStart) - if len(errors) != 0 { - // these are unrecoverable errors--exit to report them as diagnostics - for _, e := range errors { - reportDiagnostic(e) - } - return tsc.CommandLineResult{Status: tsc.ExitStatusDiagnosticsPresent_OutputsGenerated} - } - configForCompilation = configParseResult - // Updater to reflect pretty - reportDiagnostic = tsc.CreateDiagnosticReporter(sys, sys.Writer(), commandLine.CompilerOptions()) - } - - reportErrorSummary := tsc.CreateReportErrorSummary(sys, configForCompilation.CompilerOptions()) - if compilerOptionsFromCommandLine.ShowConfig.IsTrue() { - showConfig(sys, configForCompilation.CompilerOptions()) - return tsc.CommandLineResult{Status: tsc.ExitStatusSuccess} - } - if configForCompilation.CompilerOptions().Watch.IsTrue() { - watcher := createWatcher(sys, configForCompilation, reportDiagnostic, reportErrorSummary, testing) - watcher.start() - return tsc.CommandLineResult{Status: tsc.ExitStatusSuccess, Watcher: watcher} - } else if configForCompilation.CompilerOptions().IsIncremental() { - return performIncrementalCompilation( - sys, - configForCompilation, - reportDiagnostic, - reportErrorSummary, - extendedConfigCache, - &compileTimes, - testing, - ) - } - return performCompilation( - sys, - configForCompilation, - reportDiagnostic, - reportErrorSummary, - extendedConfigCache, - &compileTimes, - testing, - ) -} - -func findConfigFile(searchPath string, fileExists func(string) bool, configName string) string { - result, ok := tspath.ForEachAncestorDirectory(searchPath, func(ancestor string) (string, bool) { - fullConfigName := tspath.CombinePaths(ancestor, configName) - if fileExists(fullConfigName) { - return fullConfigName, true - } - return fullConfigName, false - }) - if !ok { - return "" - } - return result -} - -func getTraceFromSys(sys tsc.System, testing tsc.CommandLineTesting) func(msg string) { - return tsc.GetTraceWithWriterFromSys(sys.Writer(), testing) -} - -func performIncrementalCompilation( - sys tsc.System, - config *tsoptions.ParsedCommandLine, - reportDiagnostic tsc.DiagnosticReporter, - reportErrorSummary tsc.DiagnosticsReporter, - extendedConfigCache tsoptions.ExtendedConfigCache, - compileTimes *tsc.CompileTimes, - testing tsc.CommandLineTesting, -) tsc.CommandLineResult { - host := compiler.NewCachedFSCompilerHost(sys.GetCurrentDirectory(), sys.FS(), sys.DefaultLibraryPath(), extendedConfigCache, getTraceFromSys(sys, testing)) - buildInfoReadStart := sys.Now() - oldProgram := incremental.ReadBuildInfoProgram(config, incremental.NewBuildInfoReader(host), host) - compileTimes.BuildInfoReadTime = sys.Now().Sub(buildInfoReadStart) - // todo: cache, statistics, tracing - parseStart := sys.Now() - program := compiler.NewProgram(compiler.ProgramOptions{ - Config: config, - Host: host, - JSDocParsingMode: ast.JSDocParsingModeParseForTypeErrors, - }) - compileTimes.ParseTime = sys.Now().Sub(parseStart) - changesComputeStart := sys.Now() - incrementalProgram := incremental.NewProgram(program, oldProgram, incremental.CreateHost(host), testing != nil) - compileTimes.ChangesComputeTime = sys.Now().Sub(changesComputeStart) - result, _ := tsc.EmitAndReportStatistics(tsc.EmitInput{ - Sys: sys, - ProgramLike: incrementalProgram, - Program: incrementalProgram.GetProgram(), - Config: config, - ReportDiagnostic: reportDiagnostic, - ReportErrorSummary: reportErrorSummary, - Writer: sys.Writer(), - CompileTimes: compileTimes, - Testing: testing, - }) - if testing != nil { - testing.OnProgram(incrementalProgram) - } - return tsc.CommandLineResult{ - Status: result.Status, - } -} - -func performCompilation( - sys tsc.System, - config *tsoptions.ParsedCommandLine, - reportDiagnostic tsc.DiagnosticReporter, - reportErrorSummary tsc.DiagnosticsReporter, - extendedConfigCache tsoptions.ExtendedConfigCache, - compileTimes *tsc.CompileTimes, - testing tsc.CommandLineTesting, -) tsc.CommandLineResult { - host := compiler.NewCachedFSCompilerHost(sys.GetCurrentDirectory(), sys.FS(), sys.DefaultLibraryPath(), extendedConfigCache, getTraceFromSys(sys, testing)) - // todo: cache, statistics, tracing - parseStart := sys.Now() - program := compiler.NewProgram(compiler.ProgramOptions{ - Config: config, - Host: host, - JSDocParsingMode: ast.JSDocParsingModeParseForTypeErrors, - }) - compileTimes.ParseTime = sys.Now().Sub(parseStart) - result, _ := tsc.EmitAndReportStatistics(tsc.EmitInput{ - Sys: sys, - ProgramLike: program, - Program: program, - Config: config, - ReportDiagnostic: reportDiagnostic, - ReportErrorSummary: reportErrorSummary, - Writer: sys.Writer(), - CompileTimes: compileTimes, - Testing: testing, - }) - return tsc.CommandLineResult{ - Status: result.Status, - } -} - -func showConfig(sys tsc.System, config *core.CompilerOptions) { - // !!! - _ = jsonutil.MarshalIndentWrite(sys.Writer(), config, "", " ") -} diff --git a/kitcom/internal/tsgo/execute/tsc/compile.go b/kitcom/internal/tsgo/execute/tsc/compile.go deleted file mode 100644 index 58b3215..0000000 --- a/kitcom/internal/tsgo/execute/tsc/compile.go +++ /dev/null @@ -1,78 +0,0 @@ -package tsc - -import ( - "io" - "time" - - "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/execute/incremental" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/vfs" -) - -type System interface { - Writer() io.Writer - FS() vfs.FS - DefaultLibraryPath() string - GetCurrentDirectory() string - WriteOutputIsTTY() bool - GetWidthOfTerminal() int - GetEnvironmentVariable(name string) string - - Now() time.Time - SinceStart() time.Duration -} - -type ExitStatus int - -const ( - ExitStatusSuccess ExitStatus = 0 - ExitStatusDiagnosticsPresent_OutputsGenerated ExitStatus = 1 - ExitStatusDiagnosticsPresent_OutputsSkipped ExitStatus = 2 - ExitStatusInvalidProject_OutputsSkipped ExitStatus = 3 - ExitStatusProjectReferenceCycle_OutputsSkipped ExitStatus = 4 - ExitStatusNotImplemented ExitStatus = 5 -) - -type Watcher interface { - DoCycle() -} - -type CommandLineResult struct { - Status ExitStatus - Watcher Watcher -} - -type CommandLineTesting interface { - // Ensure that all emitted files are timestamped in order to ensure they are deterministic for test baseline - OnEmittedFiles(result *compiler.EmitResult, mTimesCache *collections.SyncMap[tspath.Path, time.Time]) - OnListFilesStart(w io.Writer) - OnListFilesEnd(w io.Writer) - OnStatisticsStart(w io.Writer) - OnStatisticsEnd(w io.Writer) - OnBuildStatusReportStart(w io.Writer) - OnBuildStatusReportEnd(w io.Writer) - OnWatchStatusReportStart() - OnWatchStatusReportEnd() - GetTrace(w io.Writer) func(msg string) - OnProgram(program *incremental.Program) -} - -type CompileTimes struct { - ConfigTime time.Duration - ParseTime time.Duration - bindTime time.Duration - checkTime time.Duration - totalTime time.Duration - emitTime time.Duration - BuildInfoReadTime time.Duration - ChangesComputeTime time.Duration -} -type CompileAndEmitResult struct { - Diagnostics []*ast.Diagnostic - EmitResult *compiler.EmitResult - Status ExitStatus - times *CompileTimes -} diff --git a/kitcom/internal/tsgo/execute/tsc/diagnostics.go b/kitcom/internal/tsgo/execute/tsc/diagnostics.go deleted file mode 100644 index 6004d64..0000000 --- a/kitcom/internal/tsgo/execute/tsc/diagnostics.go +++ /dev/null @@ -1,166 +0,0 @@ -package tsc - -import ( - "fmt" - "io" - "strings" - - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/core" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/diagnosticwriter" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath" -) - -func getFormatOptsOfSys(sys System) *diagnosticwriter.FormattingOptions { - return &diagnosticwriter.FormattingOptions{ - NewLine: "\n", - ComparePathsOptions: tspath.ComparePathsOptions{ - CurrentDirectory: sys.GetCurrentDirectory(), - UseCaseSensitiveFileNames: sys.FS().UseCaseSensitiveFileNames(), - }, - } -} - -type DiagnosticReporter = func(*ast.Diagnostic) - -func QuietDiagnosticReporter(diagnostic *ast.Diagnostic) {} - -func CreateDiagnosticReporter(sys System, w io.Writer, options *core.CompilerOptions) DiagnosticReporter { - if options.Quiet.IsTrue() { - return QuietDiagnosticReporter - } - formatOpts := getFormatOptsOfSys(sys) - if shouldBePretty(sys, options) { - return func(diagnostic *ast.Diagnostic) { - diagnosticwriter.FormatDiagnosticWithColorAndContext(w, diagnostic, formatOpts) - fmt.Fprint(w, formatOpts.NewLine) - } - } - return func(diagnostic *ast.Diagnostic) { - diagnosticwriter.WriteFormatDiagnostic(w, diagnostic, formatOpts) - } -} - -func defaultIsPretty(sys System) bool { - return sys.WriteOutputIsTTY() && sys.GetEnvironmentVariable("NO_COLOR") == "" -} - -func shouldBePretty(sys System, options *core.CompilerOptions) bool { - if options == nil || options.Pretty.IsUnknown() { - return defaultIsPretty(sys) - } - return options.Pretty.IsTrue() -} - -type colors struct { - showColors bool - - isWindows bool - isWindowsTerminal bool - isVSCode bool - supportsRicherColors bool -} - -func createColors(sys System) *colors { - if !defaultIsPretty(sys) { - return &colors{showColors: false} - } - - os := sys.GetEnvironmentVariable("OS") - isWindows := strings.Contains(strings.ToLower(os), "windows") - isWindowsTerminal := sys.GetEnvironmentVariable("WT_SESSION") != "" - isVSCode := sys.GetEnvironmentVariable("TERM_PROGRAM") == "vscode" - supportsRicherColors := sys.GetEnvironmentVariable("COLORTERM") == "truecolor" || sys.GetEnvironmentVariable("TERM") == "xterm-256color" - - return &colors{ - showColors: true, - isWindows: isWindows, - isWindowsTerminal: isWindowsTerminal, - isVSCode: isVSCode, - supportsRicherColors: supportsRicherColors, - } -} - -func (c *colors) bold(str string) string { - if !c.showColors { - return str - } - return "\x1b[1m" + str + "\x1b[22m" -} - -func (c *colors) blue(str string) string { - if !c.showColors { - return str - } - - // Effectively Powershell and Command prompt users use cyan instead - // of blue because the default theme doesn't show blue with enough contrast. - if c.isWindows && !c.isWindowsTerminal && !c.isVSCode { - return c.brightWhite(str) - } - return "\x1b[94m" + str + "\x1b[39m" -} - -func (c *colors) blueBackground(str string) string { - if !c.showColors { - return str - } - if c.supportsRicherColors { - return "\x1B[48;5;68m" + str + "\x1B[39;49m" - } else { - return "\x1b[44m" + str + "\x1B[39;49m" - } -} - -func (c *colors) brightWhite(str string) string { - if !c.showColors { - return str - } - return "\x1b[97m" + str + "\x1b[39m" -} - -type DiagnosticsReporter = func(diagnostics []*ast.Diagnostic) - -func QuietDiagnosticsReporter(diagnostics []*ast.Diagnostic) {} - -func CreateReportErrorSummary(sys System, options *core.CompilerOptions) DiagnosticsReporter { - if shouldBePretty(sys, options) { - formatOpts := getFormatOptsOfSys(sys) - return func(diagnostics []*ast.Diagnostic) { - diagnosticwriter.WriteErrorSummaryText(sys.Writer(), diagnostics, formatOpts) - } - } - return QuietDiagnosticsReporter -} - -func CreateBuilderStatusReporter(sys System, w io.Writer, options *core.CompilerOptions, testing CommandLineTesting) DiagnosticReporter { - if options.Quiet.IsTrue() { - return QuietDiagnosticReporter - } - - formatOpts := getFormatOptsOfSys(sys) - writeStatus := core.IfElse(shouldBePretty(sys, options), diagnosticwriter.FormatDiagnosticsStatusWithColorAndTime, diagnosticwriter.FormatDiagnosticsStatusAndTime) - return func(diagnostic *ast.Diagnostic) { - if testing != nil { - testing.OnBuildStatusReportStart(w) - defer testing.OnBuildStatusReportEnd(w) - } - writeStatus(w, sys.Now().Format("03:04:05 PM"), diagnostic, formatOpts) - fmt.Fprint(w, formatOpts.NewLine, formatOpts.NewLine) - } -} - -func CreateWatchStatusReporter(sys System, options *core.CompilerOptions, testing CommandLineTesting) DiagnosticReporter { - formatOpts := getFormatOptsOfSys(sys) - writeStatus := core.IfElse(shouldBePretty(sys, options), diagnosticwriter.FormatDiagnosticsStatusWithColorAndTime, diagnosticwriter.FormatDiagnosticsStatusAndTime) - return func(diagnostic *ast.Diagnostic) { - writer := sys.Writer() - if testing != nil { - testing.OnWatchStatusReportStart() - defer testing.OnWatchStatusReportEnd() - } - diagnosticwriter.TryClearScreen(writer, diagnostic, options) - writeStatus(writer, sys.Now().Format("03:04:05 PM"), diagnostic, formatOpts) - fmt.Fprint(writer, formatOpts.NewLine, formatOpts.NewLine) - } -} diff --git a/kitcom/internal/tsgo/execute/tsc/emit.go b/kitcom/internal/tsgo/execute/tsc/emit.go deleted file mode 100644 index 5ee0507..0000000 --- a/kitcom/internal/tsgo/execute/tsc/emit.go +++ /dev/null @@ -1,142 +0,0 @@ -package tsc - -import ( - "context" - "fmt" - "io" - "runtime" - "time" - - "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/tsoptions" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath" -) - -func GetTraceWithWriterFromSys(w io.Writer, testing CommandLineTesting) func(msg string) { - if testing == nil { - return func(msg string) { - fmt.Fprintln(w, msg) - } - } else { - return testing.GetTrace(w) - } -} - -type EmitInput struct { - Sys System - ProgramLike compiler.ProgramLike - Program *compiler.Program - Config *tsoptions.ParsedCommandLine - ReportDiagnostic DiagnosticReporter - ReportErrorSummary DiagnosticsReporter - Writer io.Writer - WriteFile compiler.WriteFile - CompileTimes *CompileTimes - Testing CommandLineTesting - TestingMTimesCache *collections.SyncMap[tspath.Path, time.Time] -} - -func EmitAndReportStatistics(input EmitInput) (CompileAndEmitResult, *Statistics) { - var statistics *Statistics - result := EmitFilesAndReportErrors(input) - if result.Status != ExitStatusSuccess { - // compile exited early - return result, nil - } - result.times.totalTime = input.Sys.SinceStart() - - if input.Config.CompilerOptions().Diagnostics.IsTrue() || input.Config.CompilerOptions().ExtendedDiagnostics.IsTrue() { - var memStats runtime.MemStats - // GC must be called twice to allow things to settle. - runtime.GC() - runtime.GC() - runtime.ReadMemStats(&memStats) - - statistics = statisticsFromProgram(input, &memStats) - statistics.Report(input.Writer, input.Testing) - } - - if result.EmitResult.EmitSkipped && len(result.Diagnostics) > 0 { - result.Status = ExitStatusDiagnosticsPresent_OutputsSkipped - } else if len(result.Diagnostics) > 0 { - result.Status = ExitStatusDiagnosticsPresent_OutputsGenerated - } - return result, statistics -} - -func EmitFilesAndReportErrors(input EmitInput) (result CompileAndEmitResult) { - result.times = input.CompileTimes - ctx := context.Background() - - allDiagnostics := compiler.GetDiagnosticsOfAnyProgram( - ctx, - input.ProgramLike, - nil, - false, - func(ctx context.Context, file *ast.SourceFile) []*ast.Diagnostic { - // Options diagnostics include global diagnostics (even though we collect them separately), - // and global diagnostics create checkers, which then bind all of the files. Do this binding - // early so we can track the time. - bindStart := input.Sys.Now() - diags := input.ProgramLike.GetBindDiagnostics(ctx, file) - result.times.bindTime = input.Sys.Now().Sub(bindStart) - return diags - }, - func(ctx context.Context, file *ast.SourceFile) []*ast.Diagnostic { - checkStart := input.Sys.Now() - diags := input.ProgramLike.GetSemanticDiagnostics(ctx, file) - result.times.checkTime = input.Sys.Now().Sub(checkStart) - return diags - }, - ) - - emitResult := &compiler.EmitResult{EmitSkipped: true, Diagnostics: []*ast.Diagnostic{}} - if !input.ProgramLike.Options().ListFilesOnly.IsTrue() { - emitStart := input.Sys.Now() - emitResult = input.ProgramLike.Emit(ctx, compiler.EmitOptions{ - WriteFile: input.WriteFile, - }) - result.times.emitTime = input.Sys.Now().Sub(emitStart) - } - if emitResult != nil { - allDiagnostics = append(allDiagnostics, emitResult.Diagnostics...) - } - if input.Testing != nil { - input.Testing.OnEmittedFiles(emitResult, input.TestingMTimesCache) - } - - allDiagnostics = compiler.SortAndDeduplicateDiagnostics(allDiagnostics) - for _, diagnostic := range allDiagnostics { - input.ReportDiagnostic(diagnostic) - } - - listFiles(input, emitResult) - - input.ReportErrorSummary(allDiagnostics) - result.Diagnostics = allDiagnostics - result.EmitResult = emitResult - result.Status = ExitStatusSuccess - return result -} - -func listFiles(input EmitInput, emitResult *compiler.EmitResult) { - if input.Testing != nil { - input.Testing.OnListFilesStart(input.Writer) - defer input.Testing.OnListFilesEnd(input.Writer) - } - options := input.Program.Options() - if options.ListEmittedFiles.IsTrue() { - for _, file := range emitResult.EmittedFiles { - fmt.Fprintln(input.Writer, "TSFILE: ", tspath.GetNormalizedAbsolutePath(file, input.Program.GetCurrentDirectory())) - } - } - if options.ExplainFiles.IsTrue() { - input.Program.ExplainFiles(input.Writer) - } else if options.ListFiles.IsTrue() || options.ListFilesOnly.IsTrue() { - for _, file := range input.Program.GetSourceFiles() { - fmt.Fprintln(input.Writer, file.FileName()) - } - } -} diff --git a/kitcom/internal/tsgo/execute/tsc/extendedconfigcache.go b/kitcom/internal/tsgo/execute/tsc/extendedconfigcache.go deleted file mode 100644 index 227c1d2..0000000 --- a/kitcom/internal/tsgo/execute/tsc/extendedconfigcache.go +++ /dev/null @@ -1,45 +0,0 @@ -package tsc - -import ( - "sync" - - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/collections" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tsoptions" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath" -) - -// extendedConfigCache is a minimal implementation of tsoptions.ExtendedConfigCache. -// It is concurrency-safe, but stores cached entries permanently. This implementation -// should not be used for long-running processes where configuration changes over the -// course of multiple compilations. -type ExtendedConfigCache struct { - m collections.SyncMap[tspath.Path, *extendedConfigCacheEntry] -} - -type extendedConfigCacheEntry struct { - *tsoptions.ExtendedConfigCacheEntry - mu sync.Mutex -} - -var _ tsoptions.ExtendedConfigCache = (*ExtendedConfigCache)(nil) - -// GetExtendedConfig implements tsoptions.ExtendedConfigCache. -func (e *ExtendedConfigCache) GetExtendedConfig(fileName string, path tspath.Path, parse func() *tsoptions.ExtendedConfigCacheEntry) *tsoptions.ExtendedConfigCacheEntry { - entry, loaded := e.loadOrStoreNewLockedEntry(path) - defer entry.mu.Unlock() - if !loaded { - entry.ExtendedConfigCacheEntry = parse() - } - return entry.ExtendedConfigCacheEntry -} - -// loadOrStoreNewLockedEntry loads an existing entry or creates a new one. The returned entry's mutex is locked. -func (c *ExtendedConfigCache) loadOrStoreNewLockedEntry(path tspath.Path) (*extendedConfigCacheEntry, bool) { - entry := &extendedConfigCacheEntry{} - entry.mu.Lock() - if existing, loaded := c.m.LoadOrStore(path, entry); loaded { - existing.mu.Lock() - return existing, true - } - return entry, false -} diff --git a/kitcom/internal/tsgo/execute/tsc/help.go b/kitcom/internal/tsgo/execute/tsc/help.go deleted file mode 100644 index 21367a8..0000000 --- a/kitcom/internal/tsgo/execute/tsc/help.go +++ /dev/null @@ -1,394 +0,0 @@ -package tsc - -import ( - "fmt" - "slices" - "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/tsoptions" -) - -func PrintVersion(sys System) { - fmt.Fprintln(sys.Writer(), diagnostics.Version_0.Format(core.Version())) -} - -func PrintHelp(sys System, commandLine *tsoptions.ParsedCommandLine) { - if commandLine.CompilerOptions().All.IsFalseOrUnknown() { - printEasyHelp(sys, getOptionsForHelp(commandLine)) - } else { - // !!! printAllHelp(sys, getOptionsForHelp(commandLine)) - } -} - -func getOptionsForHelp(commandLine *tsoptions.ParsedCommandLine) []*tsoptions.CommandLineOption { - // Sort our options by their names, (e.g. "--noImplicitAny" comes before "--watch") - opts := slices.Clone(tsoptions.OptionsDeclarations) - opts = append(opts, &tsoptions.TscBuildOption) - - if commandLine.CompilerOptions().All.IsTrue() { - slices.SortFunc(opts, func(a, b *tsoptions.CommandLineOption) int { - return strings.Compare(strings.ToLower(a.Name), strings.ToLower(b.Name)) - }) - return opts - } else { - return core.Filter(opts, func(opt *tsoptions.CommandLineOption) bool { - return opt.ShowInSimplifiedHelpView - }) - } -} - -func getHeader(sys System, message string) []string { - colors := createColors(sys) - header := make([]string, 0, 3) - terminalWidth := sys.GetWidthOfTerminal() - const tsIcon = " " - const tsIconTS = " TS " - const tsIconLength = len(tsIcon) - - tsIconFirstLine := colors.blueBackground(tsIcon) - tsIconSecondLine := colors.blueBackground(colors.brightWhite(tsIconTS)) - // If we have enough space, print TS icon. - if terminalWidth >= len(message)+tsIconLength { - // right align of the icon is 120 at most. - rightAlign := core.IfElse(terminalWidth > 120, 120, terminalWidth) - leftAlign := rightAlign - tsIconLength - header = append(header, fmt.Sprintf("%-*s", leftAlign, message), tsIconFirstLine, "\n") - header = append(header, strings.Repeat(" ", leftAlign), tsIconSecondLine, "\n") - } else { - header = append(header, message, "\n", "\n") - } - return header -} - -func printEasyHelp(sys System, simpleOptions []*tsoptions.CommandLineOption) { - colors := createColors(sys) - var output []string - example := func(examples []string, desc *diagnostics.Message) { - for _, example := range examples { - output = append(output, " ", colors.blue(example), "\n") - } - output = append(output, " ", desc.Format(), "\n", "\n") - } - - msg := diagnostics.X_tsc_Colon_The_TypeScript_Compiler.Format() + " - " + diagnostics.Version_0.Format(core.Version()) - output = append(output, getHeader(sys, msg)...) - - output = append(output, colors.bold(diagnostics.COMMON_COMMANDS.Format()), "\n", "\n") - - example([]string{"tsc"}, diagnostics.Compiles_the_current_project_tsconfig_json_in_the_working_directory) - example([]string{"tsc app.ts util.ts"}, diagnostics.Ignoring_tsconfig_json_compiles_the_specified_files_with_default_compiler_options) - example([]string{"tsc -b"}, diagnostics.Build_a_composite_project_in_the_working_directory) - example([]string{"tsc --init"}, diagnostics.Creates_a_tsconfig_json_with_the_recommended_settings_in_the_working_directory) - example([]string{"tsc -p ./path/to/tsconfig.json"}, diagnostics.Compiles_the_TypeScript_project_located_at_the_specified_path) - example([]string{"tsc --help --all"}, diagnostics.An_expanded_version_of_this_information_showing_all_possible_compiler_options) - example([]string{"tsc --noEmit", "tsc --target esnext"}, diagnostics.Compiles_the_current_project_with_additional_settings) - - var cliCommands []*tsoptions.CommandLineOption - var configOpts []*tsoptions.CommandLineOption - for _, opt := range simpleOptions { - if opt.IsCommandLineOnly || opt.Category == diagnostics.Command_line_Options { - cliCommands = append(cliCommands, opt) - } else { - configOpts = append(configOpts, opt) - } - } - - output = append(output, generateSectionOptionsOutput(sys, diagnostics.COMMAND_LINE_FLAGS.Format(), cliCommands /*subCategory*/, false /*beforeOptionsDescription*/, nil /*afterOptionsDescription*/, nil)...) - - // !!! locale formatMessage - after := diagnostics.You_can_learn_about_all_of_the_compiler_options_at_0.Format("https://aka.ms/tsc") - output = append(output, generateSectionOptionsOutput(sys, diagnostics.COMMON_COMPILER_OPTIONS.Format(), configOpts /*subCategory*/, false /*beforeOptionsDescription*/, nil, &after)...) - - for _, chunk := range output { - fmt.Fprint(sys.Writer(), chunk) - } -} - -func PrintBuildHelp(sys System, buildOptions []*tsoptions.CommandLineOption) { - var output []string - output = append(output, getHeader(sys, diagnostics.X_tsc_Colon_The_TypeScript_Compiler.Format()+" - "+diagnostics.Version_0.Format(core.Version()))...) - before := diagnostics.Using_build_b_will_make_tsc_behave_more_like_a_build_orchestrator_than_a_compiler_This_is_used_to_trigger_building_composite_projects_which_you_can_learn_more_about_at_0.Format("https://aka.ms/tsc-composite-builds") - options := core.Filter(buildOptions, func(option *tsoptions.CommandLineOption) bool { - return option != &tsoptions.TscBuildOption - }) - output = append(output, generateSectionOptionsOutput(sys, diagnostics.BUILD_OPTIONS.Format(), options, false, &before, nil)...) - - for _, chunk := range output { - fmt.Fprint(sys.Writer(), chunk) - } -} - -func generateSectionOptionsOutput( - sys System, - sectionName string, - options []*tsoptions.CommandLineOption, - subCategory bool, - beforeOptionsDescription, - afterOptionsDescription *string, -) (output []string) { - output = append(output, createColors(sys).bold(sectionName), "\n", "\n") - - if beforeOptionsDescription != nil { - output = append(output, *beforeOptionsDescription, "\n", "\n") - } - if !subCategory { - output = append(output, generateGroupOptionOutput(sys, options)...) - if afterOptionsDescription != nil { - output = append(output, *afterOptionsDescription, "\n", "\n") - } - return output - } - categoryMap := make(map[string][]*tsoptions.CommandLineOption) - for _, option := range options { - if option.Category == nil { - continue - } - curCategory := option.Category.Format() - categoryMap[curCategory] = append(categoryMap[curCategory], option) - } - for key, value := range categoryMap { - output = append(output, "### ", key, "\n", "\n") - output = append(output, generateGroupOptionOutput(sys, value)...) - } - if afterOptionsDescription != nil { - output = append(output, *afterOptionsDescription, "\n", "\n") - } - - return output -} - -func generateGroupOptionOutput(sys System, optionsList []*tsoptions.CommandLineOption) []string { - var maxLength int - for _, option := range optionsList { - curLenght := len(getDisplayNameTextOfOption(option)) - maxLength = max(curLenght, maxLength) - } - - // left part should be right align, right part should be left align - - // assume 2 space between left margin and left part. - rightAlignOfLeftPart := maxLength + 2 - // assume 2 space between left and right part - leftAlignOfRightPart := rightAlignOfLeftPart + 2 - - var lines []string - for _, option := range optionsList { - tmp := generateOptionOutput(sys, option, rightAlignOfLeftPart, leftAlignOfRightPart) - lines = append(lines, tmp...) - } - - // make sure always a blank line in the end. - if len(lines) < 2 || lines[len(lines)-2] != "\n" { - lines = append(lines, "\n") - } - - return lines -} - -func generateOptionOutput( - sys System, - option *tsoptions.CommandLineOption, - rightAlignOfLeft, leftAlignOfRight int, -) []string { - var text []string - colors := createColors(sys) - - // name and description - name := getDisplayNameTextOfOption(option) - - // value type and possible value - valueCandidates := getValueCandidate(option) - - var defaultValueDescription string - if msg, ok := option.DefaultValueDescription.(*diagnostics.Message); ok && msg != nil { - defaultValueDescription = msg.Format() - } else { - defaultValueDescription = formatDefaultValue( - option.DefaultValueDescription, - core.IfElse( - option.Kind == tsoptions.CommandLineOptionTypeList || option.Kind == tsoptions.CommandLineOptionTypeListOrElement, - option.Elements(), option, - ), - ) - } - - terminalWidth := sys.GetWidthOfTerminal() - - if terminalWidth >= 80 { - description := "" - if option.Description != nil { - description = option.Description.Format() - } - text = append(text, getPrettyOutput(colors, name, description, rightAlignOfLeft, leftAlignOfRight, terminalWidth, true /*colorLeft*/)...) - text = append(text, "\n") - if showAdditionalInfoOutput(valueCandidates, option) { - if valueCandidates != nil { - text = append(text, getPrettyOutput(colors, valueCandidates.valueType, valueCandidates.possibleValues, rightAlignOfLeft, leftAlignOfRight, terminalWidth, false /*colorLeft*/)...) - text = append(text, "\n") - } - if defaultValueDescription != "" { - text = append(text, getPrettyOutput(colors, diagnostics.X_default_Colon.Format(), defaultValueDescription, rightAlignOfLeft, leftAlignOfRight, terminalWidth, false /*colorLeft*/)...) - text = append(text, "\n") - } - } - text = append(text, "\n") - } else { - text = append(text, colors.blue(name), "\n") - if option.Description != nil { - text = append(text, option.Description.Format()) - } - text = append(text, "\n") - if showAdditionalInfoOutput(valueCandidates, option) { - if valueCandidates != nil { - text = append(text, valueCandidates.valueType, " ", valueCandidates.possibleValues) - } - if defaultValueDescription != "" { - if valueCandidates != nil { - text = append(text, "\n") - } - text = append(text, diagnostics.X_default_Colon.Format(), " ", defaultValueDescription) - } - - text = append(text, "\n") - } - text = append(text, "\n") - } - - return text -} - -func formatDefaultValue(defaultValue any, option *tsoptions.CommandLineOption) string { - if defaultValue == nil || defaultValue == core.TSUnknown { - return "undefined" - } - - if option.Kind == tsoptions.CommandLineOptionTypeEnum { - // e.g. ScriptTarget.ES2015 -> "es6/es2015" - var names []string - for name, value := range option.EnumMap().Entries() { - if value == defaultValue { - names = append(names, name) - } - } - return strings.Join(names, "/") - } - return fmt.Sprintf("%v", defaultValue) -} - -type valueCandidate struct { - // "one or more" or "any of" - valueType string - possibleValues string -} - -func showAdditionalInfoOutput(valueCandidates *valueCandidate, option *tsoptions.CommandLineOption) bool { - if option.Category == diagnostics.Command_line_Options { - return false - } - if valueCandidates != nil && valueCandidates.possibleValues == "string" && - (option.DefaultValueDescription == nil || - option.DefaultValueDescription == "false" || - option.DefaultValueDescription == "n/a") { - return false - } - return true -} - -func getValueCandidate(option *tsoptions.CommandLineOption) *valueCandidate { - // option.type might be "string" | "number" | "boolean" | "object" | "list" | Map - // string -- any of: string - // number -- any of: number - // boolean -- any of: boolean - // object -- null - // list -- one or more: , content depends on `option.element.type`, the same as others - // Map -- any of: key1, key2, .... - if option.Kind == tsoptions.CommandLineOptionTypeObject { - return nil - } - - res := &valueCandidate{} - if option.Kind == tsoptions.CommandLineOptionTypeListOrElement { - // assert(option.type !== "listOrElement") - panic("no value candidate for list or element") - } - - switch option.Kind { - case tsoptions.CommandLineOptionTypeString, - tsoptions.CommandLineOptionTypeNumber, - tsoptions.CommandLineOptionTypeBoolean: - res.valueType = diagnostics.X_type_Colon.Format() - case tsoptions.CommandLineOptionTypeList: - res.valueType = diagnostics.X_one_or_more_Colon.Format() - default: - res.valueType = diagnostics.X_one_of_Colon.Format() - } - - res.possibleValues = getPossibleValues(option) - - return res -} - -func getPossibleValues(option *tsoptions.CommandLineOption) string { - switch option.Kind { - case tsoptions.CommandLineOptionTypeString, - tsoptions.CommandLineOptionTypeNumber, - tsoptions.CommandLineOptionTypeBoolean: - return string(option.Kind) - case tsoptions.CommandLineOptionTypeList, - tsoptions.CommandLineOptionTypeListOrElement: - return getPossibleValues(option.Elements()) - case tsoptions.CommandLineOptionTypeObject: - return "" - default: - // Map - // Group synonyms: es6/es2015 - enumMap := option.EnumMap() - inverted := collections.NewOrderedMapWithSizeHint[any, []string](enumMap.Size()) - deprecatedKeys := option.DeprecatedKeys() - - for name, value := range enumMap.Entries() { - if deprecatedKeys == nil || !deprecatedKeys.Has(name) { - inverted.Set(value, append(inverted.GetOrZero(value), name)) - } - } - var syns []string - for synonyms := range inverted.Values() { - syns = append(syns, strings.Join(synonyms, "/")) - } - return strings.Join(syns, ", ") - } -} - -func getPrettyOutput(colors *colors, left string, right string, rightAlignOfLeft int, leftAlignOfRight int, terminalWidth int, colorLeft bool) []string { - // !!! How does terminalWidth interact with UTF-8 encoding? Strada just assumed UTF-16. - res := make([]string, 0, 4) - isFirstLine := true - remainRight := right - rightCharacterNumber := terminalWidth - leftAlignOfRight - for len(remainRight) > 0 { - curLeft := "" - if isFirstLine { - curLeft = fmt.Sprintf("%*s", rightAlignOfLeft, left) - curLeft = fmt.Sprintf("%-*s", leftAlignOfRight, curLeft) - if colorLeft { - curLeft = colors.blue(curLeft) - } - } else { - curLeft = strings.Repeat(" ", leftAlignOfRight) - } - - idx := min(rightCharacterNumber, len(remainRight)) - curRight := remainRight[:idx] - remainRight = remainRight[idx:] - res = append(res, curLeft, curRight, "\n") - isFirstLine = false - } - return res -} - -func getDisplayNameTextOfOption(option *tsoptions.CommandLineOption) string { - return "--" + option.Name + core.IfElse(option.ShortName != "", ", -"+option.ShortName, "") -} diff --git a/kitcom/internal/tsgo/execute/tsc/statistics.go b/kitcom/internal/tsgo/execute/tsc/statistics.go deleted file mode 100644 index cfafb02..0000000 --- a/kitcom/internal/tsgo/execute/tsc/statistics.go +++ /dev/null @@ -1,157 +0,0 @@ -package tsc - -import ( - "fmt" - "io" - "runtime" - "strconv" - "time" - - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/compiler" -) - -type tableRow struct { - name string - value string -} - -type table struct { - rows []tableRow -} - -func (t *table) add(name string, value any) { - if d, ok := value.(time.Duration); ok { - value = formatDuration(d) - } - t.rows = append(t.rows, tableRow{name, fmt.Sprint(value)}) -} - -func (t *table) print(w io.Writer) { - nameWidth := 0 - valueWidth := 0 - for _, r := range t.rows { - nameWidth = max(nameWidth, len(r.name)) - valueWidth = max(valueWidth, len(r.value)) - } - - for _, r := range t.rows { - fmt.Fprintf(w, "%-*s %*s\n", nameWidth+1, r.name+":", valueWidth, r.value) - } -} - -func formatDuration(d time.Duration) string { - return fmt.Sprintf("%.3fs", d.Seconds()) -} - -func identifierCount(p *compiler.Program) int { - count := 0 - for _, file := range p.SourceFiles() { - count += file.IdentifierCount - } - return count -} - -type Statistics struct { - isAggregate bool - Projects int - ProjectsBuilt int - TimestampUpdates int - files int - lines int - identifiers int - symbols int - types int - instantiations int - memoryUsed uint64 - memoryAllocs uint64 - compileTimes *CompileTimes -} - -func statisticsFromProgram(input EmitInput, memStats *runtime.MemStats) *Statistics { - return &Statistics{ - files: len(input.Program.SourceFiles()), - lines: input.Program.LineCount(), - identifiers: input.Program.IdentifierCount(), - symbols: input.Program.SymbolCount(), - types: input.Program.TypeCount(), - instantiations: input.Program.InstantiationCount(), - memoryUsed: memStats.Alloc, - memoryAllocs: memStats.Mallocs, - compileTimes: input.CompileTimes, - } -} - -func (s *Statistics) Report(w io.Writer, testing CommandLineTesting) { - if testing != nil { - testing.OnStatisticsStart(w) - defer testing.OnStatisticsEnd(w) - } - var table table - var prefix string - - if s.isAggregate { - prefix = "Aggregate " - table.add("Projects in scope", s.Projects) - table.add("Projects built", s.ProjectsBuilt) - table.add("Timestamps only updates", s.TimestampUpdates) - } - table.add(prefix+"Files", s.files) - table.add(prefix+"Lines", s.lines) - table.add(prefix+"Identifiers", s.identifiers) - table.add(prefix+"Symbols", s.symbols) - table.add(prefix+"Types", s.types) - table.add(prefix+"Instantiations", s.instantiations) - table.add(prefix+"Memory used", fmt.Sprintf("%vK", s.memoryUsed/1024)) - table.add(prefix+"Memory allocs", strconv.FormatUint(s.memoryAllocs, 10)) - if s.compileTimes.ConfigTime != 0 { - table.add(prefix+"Config time", s.compileTimes.ConfigTime) - } - if s.compileTimes.BuildInfoReadTime != 0 { - table.add(prefix+"BuildInfo read time", s.compileTimes.BuildInfoReadTime) - } - table.add(prefix+"Parse time", s.compileTimes.ParseTime) - if s.compileTimes.bindTime != 0 { - table.add(prefix+"Bind time", s.compileTimes.bindTime) - } - if s.compileTimes.checkTime != 0 { - table.add(prefix+"Check time", s.compileTimes.checkTime) - } - if s.compileTimes.emitTime != 0 { - table.add(prefix+"Emit time", s.compileTimes.emitTime) - } - if s.compileTimes.ChangesComputeTime != 0 { - table.add(prefix+"Changes compute time", s.compileTimes.ChangesComputeTime) - } - table.add(prefix+"Total time", s.compileTimes.totalTime) - table.print(w) -} - -func (s *Statistics) Aggregate(stat *Statistics) { - s.isAggregate = true - if s.compileTimes == nil { - s.compileTimes = &CompileTimes{} - } - // Aggregate statistics - s.files += stat.files - s.lines += stat.lines - s.identifiers += stat.identifiers - s.symbols += stat.symbols - s.types += stat.types - s.instantiations += stat.instantiations - s.memoryUsed += stat.memoryUsed - s.memoryAllocs += stat.memoryAllocs - s.compileTimes.ConfigTime += stat.compileTimes.ConfigTime - s.compileTimes.BuildInfoReadTime += stat.compileTimes.BuildInfoReadTime - s.compileTimes.ParseTime += stat.compileTimes.ParseTime - s.compileTimes.bindTime += stat.compileTimes.bindTime - s.compileTimes.checkTime += stat.compileTimes.checkTime - s.compileTimes.emitTime += stat.compileTimes.emitTime - s.compileTimes.ChangesComputeTime += stat.compileTimes.ChangesComputeTime -} - -func (s *Statistics) SetTotalTime(totalTime time.Duration) { - if s.compileTimes == nil { - s.compileTimes = &CompileTimes{} - } - s.compileTimes.totalTime = totalTime -} diff --git a/kitcom/internal/tsgo/execute/tsctests/fs.go b/kitcom/internal/tsgo/execute/tsctests/fs.go deleted file mode 100644 index 2f8ad04..0000000 --- a/kitcom/internal/tsgo/execute/tsctests/fs.go +++ /dev/null @@ -1,86 +0,0 @@ -package tsctests - -import ( - "fmt" - - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/collections" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/core" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/execute/incremental" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/testutil/harnessutil" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/vfs" - "github.com/go-json-experiment/json" -) - -type testFs struct { - vfs.FS - defaultLibs *collections.SyncSet[string] - writtenFiles collections.SyncSet[string] -} - -func (f *testFs) removeIgnoreLibPath(path string) { - if f.defaultLibs != nil && f.defaultLibs.Has(path) { - f.defaultLibs.Delete(path) - } -} - -// ReadFile reads the file specified by path and returns the content. -// If the file fails to be read, ok will be false. -func (f *testFs) ReadFile(path string) (contents string, ok bool) { - f.removeIgnoreLibPath(path) - return f.readFileHandlingBuildInfo(path) -} - -func (f *testFs) readFileHandlingBuildInfo(path string) (contents string, ok bool) { - contents, ok = f.FS.ReadFile(path) - if ok && tspath.FileExtensionIs(path, tspath.ExtensionTsBuildInfo) { - // read buildinfo and modify version - var buildInfo incremental.BuildInfo - err := json.Unmarshal([]byte(contents), &buildInfo) - if err == nil && buildInfo.Version == harnessutil.FakeTSVersion { - buildInfo.Version = core.Version() - newContents, err := json.Marshal(&buildInfo) - if err != nil { - panic("testFs.ReadFile: failed to marshal build info after fixing version: " + err.Error()) - } - contents = string(newContents) - } - } - return contents, ok -} - -func (f *testFs) WriteFile(path string, data string, writeByteOrderMark bool) error { - f.removeIgnoreLibPath(path) - f.writtenFiles.Add(path) - return f.writeFileHandlingBuildInfo(path, data, writeByteOrderMark) -} - -func (f *testFs) writeFileHandlingBuildInfo(path string, data string, writeByteOrderMark bool) error { - if tspath.FileExtensionIs(path, tspath.ExtensionTsBuildInfo) { - var buildInfo incremental.BuildInfo - if err := json.Unmarshal([]byte(data), &buildInfo); err == nil { - if buildInfo.Version == core.Version() { - // Change it to harnessutil.FakeTSVersion - buildInfo.Version = harnessutil.FakeTSVersion - newData, err := json.Marshal(&buildInfo) - if err != nil { - return fmt.Errorf("testFs.WriteFile: failed to marshal build info after fixing version: %w", err) - } - data = string(newData) - } - // Write readable build info version - if err := f.WriteFile(path+".readable.baseline.txt", toReadableBuildInfo(&buildInfo, data), false); err != nil { - return fmt.Errorf("testFs.WriteFile: failed to write readable build info: %w", err) - } - } else { - panic("testFs.WriteFile: failed to unmarshal build info: - use underlying FS's write method if this is intended use for testcase" + err.Error()) - } - } - return f.FS.WriteFile(path, data, writeByteOrderMark) -} - -// Removes `path` and all its contents. Will return the first error it encounters. -func (f *testFs) Remove(path string) error { - f.removeIgnoreLibPath(path) - return f.FS.Remove(path) -} diff --git a/kitcom/internal/tsgo/execute/tsctests/readablebuildinfo.go b/kitcom/internal/tsgo/execute/tsctests/readablebuildinfo.go deleted file mode 100644 index 8806622..0000000 --- a/kitcom/internal/tsgo/execute/tsctests/readablebuildinfo.go +++ /dev/null @@ -1,422 +0,0 @@ -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), - } - }) -} diff --git a/kitcom/internal/tsgo/execute/tsctests/runner.go b/kitcom/internal/tsgo/execute/tsctests/runner.go deleted file mode 100644 index 07b9963..0000000 --- a/kitcom/internal/tsgo/execute/tsctests/runner.go +++ /dev/null @@ -1,192 +0,0 @@ -package tsctests - -import ( - "fmt" - "path/filepath" - "slices" - "strings" - "testing" - - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/core" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/execute" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/execute/tsc" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/testutil/baseline" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath" -) - -type tscEdit struct { - caption string - commandLineArgs []string - edit func(*testSys) - expectedDiff string -} - -var noChange = &tscEdit{ - caption: "no change", -} - -var noChangeOnlyEdit = []*tscEdit{ - noChange, -} - -type tscInput struct { - subScenario string - commandLineArgs []string - files FileMap - cwd string - edits []*tscEdit - env map[string]string - ignoreCase bool - windowsStyleRoot string -} - -func (test *tscInput) executeCommand(sys *testSys, baselineBuilder *strings.Builder, commandLineArgs []string) tsc.CommandLineResult { - fmt.Fprint(baselineBuilder, "tsgo ", strings.Join(commandLineArgs, " "), "\n") - result := execute.CommandLine(sys, commandLineArgs, sys) - switch result.Status { - case tsc.ExitStatusSuccess: - baselineBuilder.WriteString("ExitStatus:: Success") - case tsc.ExitStatusDiagnosticsPresent_OutputsSkipped: - baselineBuilder.WriteString("ExitStatus:: DiagnosticsPresent_OutputsSkipped") - case tsc.ExitStatusDiagnosticsPresent_OutputsGenerated: - baselineBuilder.WriteString("ExitStatus:: DiagnosticsPresent_OutputsGenerated") - case tsc.ExitStatusInvalidProject_OutputsSkipped: - baselineBuilder.WriteString("ExitStatus:: InvalidProject_OutputsSkipped") - case tsc.ExitStatusProjectReferenceCycle_OutputsSkipped: - baselineBuilder.WriteString("ExitStatus:: ProjectReferenceCycle_OutputsSkipped") - case tsc.ExitStatusNotImplemented: - baselineBuilder.WriteString("ExitStatus:: NotImplemented") - default: - panic(fmt.Sprintf("UnknownExitStatus %d", result.Status)) - } - return result -} - -func (test *tscInput) run(t *testing.T, scenario string) { - t.Helper() - t.Run(test.getBaselineSubFolder()+"/"+test.subScenario, func(t *testing.T) { - t.Parallel() - // initial test tsc compile - baselineBuilder := &strings.Builder{} - sys := newTestSys(test, false) - fmt.Fprint( - baselineBuilder, - "currentDirectory::", - sys.GetCurrentDirectory(), - "\nuseCaseSensitiveFileNames::", - sys.FS().UseCaseSensitiveFileNames(), - "\nInput::\n", - ) - sys.baselineFSwithDiff(baselineBuilder) - result := test.executeCommand(sys, baselineBuilder, test.commandLineArgs) - sys.serializeState(baselineBuilder) - unexpectedDiff := sys.baselinePrograms(baselineBuilder, "Initial build") - - for index, do := range test.edits { - sys.clearOutput() - wg := core.NewWorkGroup(false) - var nonIncrementalSys *testSys - commandLineArgs := core.IfElse(do.commandLineArgs == nil, test.commandLineArgs, do.commandLineArgs) - wg.Queue(func() { - baselineBuilder.WriteString(fmt.Sprintf("\n\nEdit [%d]:: %s\n", index, do.caption)) - if do.edit != nil { - do.edit(sys) - } - sys.baselineFSwithDiff(baselineBuilder) - - if result.Watcher == nil { - test.executeCommand(sys, baselineBuilder, commandLineArgs) - } else { - result.Watcher.DoCycle() - } - sys.serializeState(baselineBuilder) - unexpectedDiff += sys.baselinePrograms(baselineBuilder, fmt.Sprintf("Edit [%d]:: %s\n", index, do.caption)) - }) - wg.Queue(func() { - // Compute build with all the edits - nonIncrementalSys = newTestSys(test, true) - for i := range index + 1 { - if test.edits[i].edit != nil { - test.edits[i].edit(nonIncrementalSys) - } - } - execute.CommandLine(nonIncrementalSys, commandLineArgs, nonIncrementalSys) - }) - wg.RunAndWait() - - diff := getDiffForIncremental(sys, nonIncrementalSys) - if diff != "" { - baselineBuilder.WriteString(fmt.Sprintf("\n\nDiff:: %s\n", core.IfElse(do.expectedDiff == "", "!!! Unexpected diff, please review and either fix or write explanation as expectedDiff !!!", do.expectedDiff))) - baselineBuilder.WriteString(diff) - if do.expectedDiff == "" { - unexpectedDiff += fmt.Sprintf("Edit [%d]:: %s\n!!! Unexpected diff, please review and either fix or write explanation as expectedDiff !!!\n%s\n", index, do.caption, diff) - } - } else if do.expectedDiff != "" { - baselineBuilder.WriteString(fmt.Sprintf("\n\nDiff:: %s !!! Diff not found but explanation present, please review and remove the explanation !!!\n", do.expectedDiff)) - unexpectedDiff += fmt.Sprintf("Edit [%d]:: %s\n!!! Diff not found but explanation present, please review and remove the explanation !!!\n", index, do.caption) - } - } - baseline.Run(t, strings.ReplaceAll(test.subScenario, " ", "-")+".js", baselineBuilder.String(), baseline.Options{Subfolder: filepath.Join(test.getBaselineSubFolder(), scenario)}) - if unexpectedDiff != "" { - t.Errorf("Test %s has unexpected diff %s with incremental build, please review the baseline file", test.subScenario, unexpectedDiff) - } - }) -} - -func getDiffForIncremental(incrementalSys *testSys, nonIncrementalSys *testSys) string { - var diffBuilder strings.Builder - - nonIncrementalOutputs := nonIncrementalSys.fs.writtenFiles.ToSlice() - slices.Sort(nonIncrementalOutputs) - for _, nonIncrementalOutput := range nonIncrementalOutputs { - if tspath.FileExtensionIs(nonIncrementalOutput, tspath.ExtensionTsBuildInfo) || - strings.HasSuffix(nonIncrementalOutput, ".readable.baseline.txt") { - // Just check existence - if !incrementalSys.fsFromFileMap().FileExists(nonIncrementalOutput) { - diffBuilder.WriteString(baseline.DiffText("nonIncremental "+nonIncrementalOutput, "incremental "+nonIncrementalOutput, "Exists", "")) - diffBuilder.WriteString("\n") - } - } else { - nonIncrementalText, ok := nonIncrementalSys.fsFromFileMap().ReadFile(nonIncrementalOutput) - if !ok { - panic("Written file not found " + nonIncrementalOutput) - } - incrementalText, ok := incrementalSys.fsFromFileMap().ReadFile(nonIncrementalOutput) - if !ok || incrementalText != nonIncrementalText { - diffBuilder.WriteString(baseline.DiffText("nonIncremental "+nonIncrementalOutput, "incremental "+nonIncrementalOutput, nonIncrementalText, incrementalText)) - diffBuilder.WriteString("\n") - } - } - } - - incrementalOutput := incrementalSys.getOutput(true) - nonIncrementalOutput := nonIncrementalSys.getOutput(true) - if incrementalOutput != nonIncrementalOutput { - diffBuilder.WriteString(baseline.DiffText("nonIncremental.output.txt", "incremental.output.txt", nonIncrementalOutput, incrementalOutput)) - } - return diffBuilder.String() -} - -func (test *tscInput) getBaselineSubFolder() string { - commandName := "tsc" - if slices.ContainsFunc(test.commandLineArgs, func(arg string) bool { - switch arg { - case "-b", "--b", "-build", "--build": - return true - } - return false - }) { - commandName = "tsbuild" - } - w := "" - if slices.ContainsFunc(test.commandLineArgs, func(arg string) bool { - switch arg { - case "-w", "--w", "-watch", "--watch": - return true - } - return false - }) { - w = "Watch" - } - return commandName + w -} diff --git a/kitcom/internal/tsgo/execute/tsctests/sys.go b/kitcom/internal/tsgo/execute/tsctests/sys.go deleted file mode 100644 index 37cd93f..0000000 --- a/kitcom/internal/tsgo/execute/tsctests/sys.go +++ /dev/null @@ -1,627 +0,0 @@ -package tsctests - -import ( - "fmt" - "io" - "io/fs" - "maps" - "slices" - "strconv" - "strings" - "sync" - "time" - - "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/execute/incremental" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/execute/tsc" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/testutil/harnessutil" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/testutil/stringtestutil" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tsoptions" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/vfs" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/vfs/iovfs" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/vfs/vfstest" -) - -type FileMap map[string]any - -var tscLibPath = "/home/src/tslibs/TS/Lib" - -var tscDefaultLibContent = stringtestutil.Dedent(` -/// -interface Boolean {} -interface Function {} -interface CallableFunction {} -interface NewableFunction {} -interface IArguments {} -interface Number { toExponential: any; } -interface Object {} -interface RegExp {} -interface String { charAt: any; } -interface Array { length: number; [n: number]: T; } -interface ReadonlyArray {} -interface SymbolConstructor { - (desc?: string | number): symbol; - for(name: string): symbol; - readonly toStringTag: symbol; -} -declare var Symbol: SymbolConstructor; -interface Symbol { - readonly [Symbol.toStringTag]: string; -} -declare const console: { log(msg: any): void; }; -`) - -func getTestLibPathFor(libName string) string { - var libFile string - if value, ok := tsoptions.LibMap.Get(libName); ok { - libFile = value.(string) - } else { - libFile = "lib." + libName + ".d.ts" - } - return tscLibPath + "/" + libFile -} - -type TestClock struct { - start time.Time - now time.Time - nowMu sync.Mutex -} - -func (t *TestClock) Now() time.Time { - t.nowMu.Lock() - defer t.nowMu.Unlock() - if t.now.IsZero() { - t.now = t.start - } - t.now = t.now.Add(1 * time.Second) // Simulate some time passing - return t.now -} - -func (t *TestClock) SinceStart() time.Duration { - return t.Now().Sub(t.start) -} - -func NewTscSystem(files FileMap, useCaseSensitiveFileNames bool, cwd string) *testSys { - clock := &TestClock{start: time.Now()} - return &testSys{ - fs: &testFs{ - FS: vfstest.FromMapWithClock(files, useCaseSensitiveFileNames, clock), - }, - cwd: cwd, - clock: clock, - } -} - -func newTestSys(tscInput *tscInput, forIncrementalCorrectness bool) *testSys { - cwd := tscInput.cwd - if cwd == "" { - cwd = "/home/src/workspaces/project" - } - libPath := tscLibPath - if tscInput.windowsStyleRoot != "" { - libPath = tscInput.windowsStyleRoot + libPath[1:] - } - currentWrite := &strings.Builder{} - sys := NewTscSystem(tscInput.files, !tscInput.ignoreCase, cwd) - sys.defaultLibraryPath = libPath - sys.currentWrite = currentWrite - sys.tracer = harnessutil.NewTracerForBaselining(tspath.ComparePathsOptions{ - UseCaseSensitiveFileNames: !tscInput.ignoreCase, - CurrentDirectory: cwd, - }, currentWrite) - sys.env = tscInput.env - sys.forIncrementalCorrectness = forIncrementalCorrectness - - // Ensure the default library file is present - sys.ensureLibPathExists("lib.d.ts") - for _, libFile := range tsoptions.TargetToLibMap() { - sys.ensureLibPathExists(libFile) - } - for libFile := range tsoptions.LibFilesSet.Keys() { - sys.ensureLibPathExists(libFile) - } - return sys -} - -type diffEntry struct { - content string - mTime time.Time - isWritten bool - symlinkTarget string -} - -type snapshot struct { - snap map[string]*diffEntry - defaultLibs *collections.SyncSet[string] -} - -type testSys struct { - currentWrite *strings.Builder - programBaselines strings.Builder - programIncludeBaselines strings.Builder - tracer *harnessutil.TracerForBaselining - serializedDiff *snapshot - forIncrementalCorrectness bool - - fs *testFs - defaultLibraryPath string - cwd string - env map[string]string - clock *TestClock -} - -var ( - _ tsc.System = (*testSys)(nil) - _ tsc.CommandLineTesting = (*testSys)(nil) -) - -func (s *testSys) Now() time.Time { - return s.clock.Now() -} - -func (s *testSys) SinceStart() time.Duration { - return s.clock.SinceStart() -} - -func (s *testSys) FS() vfs.FS { - return s.fs -} - -func (s *testSys) fsFromFileMap() iovfs.FsWithSys { - return s.fs.FS.(iovfs.FsWithSys) -} - -func (s *testSys) mapFs() *vfstest.MapFS { - return s.fsFromFileMap().FSys().(*vfstest.MapFS) -} - -func (s *testSys) ensureLibPathExists(path string) { - path = s.defaultLibraryPath + "/" + path - if _, ok := s.fsFromFileMap().ReadFile(path); !ok { - if s.fs.defaultLibs == nil { - s.fs.defaultLibs = &collections.SyncSet[string]{} - } - s.fs.defaultLibs.Add(path) - err := s.fsFromFileMap().WriteFile(path, tscDefaultLibContent, false) - if err != nil { - panic("Failed to write default library file: " + err.Error()) - } - } -} - -func (s *testSys) DefaultLibraryPath() string { - return s.defaultLibraryPath -} - -func (s *testSys) GetCurrentDirectory() string { - return s.cwd -} - -func (s *testSys) Writer() io.Writer { - return s.currentWrite -} - -func (s *testSys) WriteOutputIsTTY() bool { - return true -} - -func (s *testSys) GetWidthOfTerminal() int { - if widthStr := s.GetEnvironmentVariable("TS_TEST_TERMINAL_WIDTH"); widthStr != "" { - return core.Must(strconv.Atoi(widthStr)) - } - return 0 -} - -func (s *testSys) GetEnvironmentVariable(name string) string { - return s.env[name] -} - -func (s *testSys) OnEmittedFiles(result *compiler.EmitResult, mTimesCache *collections.SyncMap[tspath.Path, time.Time]) { - if result != nil { - for _, file := range result.EmittedFiles { - modTime := s.mapFs().GetModTime(file) - if s.serializedDiff != nil { - if diff, ok := s.serializedDiff.snap[file]; ok && diff.mTime.Equal(modTime) { - // Even though written, timestamp was reverted - continue - } - } - - // Ensure that the timestamp for emitted files is in the order - now := s.Now() - if err := s.fsFromFileMap().Chtimes(file, time.Time{}, now); err != nil { - panic("Failed to change time for emitted file: " + file + ": " + err.Error()) - } - // Update the mTime cache in --b mode to store the updated timestamp so tests will behave deteministically when finding newest output - if mTimesCache != nil { - path := tspath.ToPath(file, s.GetCurrentDirectory(), s.FS().UseCaseSensitiveFileNames()) - if _, found := mTimesCache.Load(path); found { - mTimesCache.Store(path, now) - } - } - } - } -} - -func (s *testSys) OnListFilesStart(w io.Writer) { - fmt.Fprintln(w, listFileStart) -} - -func (s *testSys) OnListFilesEnd(w io.Writer) { - fmt.Fprintln(w, listFileEnd) -} - -func (s *testSys) OnStatisticsStart(w io.Writer) { - fmt.Fprintln(w, statisticsStart) -} - -func (s *testSys) OnStatisticsEnd(w io.Writer) { - fmt.Fprintln(w, statisticsEnd) -} - -func (s *testSys) OnBuildStatusReportStart(w io.Writer) { - fmt.Fprintln(w, buildStatusReportStart) -} - -func (s *testSys) OnBuildStatusReportEnd(w io.Writer) { - fmt.Fprintln(w, buildStatusReportEnd) -} - -func (s *testSys) OnWatchStatusReportStart() { - fmt.Fprintln(s.Writer(), watchStatusReportStart) -} - -func (s *testSys) OnWatchStatusReportEnd() { - fmt.Fprintln(s.Writer(), watchStatusReportEnd) -} - -func (s *testSys) GetTrace(w io.Writer) func(str string) { - return func(str string) { - fmt.Fprintln(w, traceStart) - defer fmt.Fprintln(w, traceEnd) - // With tsc -b building projects in parallel we cannot serialize the package.json lookup trace - // so trace as if it wasnt cached - s.tracer.TraceWithWriter(w, str, w == s.Writer()) - } -} - -func (s *testSys) writeHeaderToBaseline(builder *strings.Builder, program *incremental.Program) { - if builder.Len() != 0 { - builder.WriteString("\n") - } - - if configFilePath := program.Options().ConfigFilePath; configFilePath != "" { - builder.WriteString(tspath.GetRelativePathFromDirectory(s.cwd, configFilePath, tspath.ComparePathsOptions{ - UseCaseSensitiveFileNames: s.FS().UseCaseSensitiveFileNames(), - CurrentDirectory: s.GetCurrentDirectory(), - }) + "::\n") - } -} - -func (s *testSys) OnProgram(program *incremental.Program) { - s.writeHeaderToBaseline(&s.programBaselines, program) - - testingData := program.GetTestingData() - s.programBaselines.WriteString("SemanticDiagnostics::\n") - for _, file := range program.GetProgram().GetSourceFiles() { - if diagnostics, ok := testingData.SemanticDiagnosticsPerFile.Load(file.Path()); ok { - if oldDiagnostics, ok := testingData.OldProgramSemanticDiagnosticsPerFile.Load(file.Path()); !ok || oldDiagnostics != diagnostics { - s.programBaselines.WriteString("*refresh* " + file.FileName() + "\n") - } - } else { - s.programBaselines.WriteString("*not cached* " + file.FileName() + "\n") - } - } - - // Write signature updates - s.programBaselines.WriteString("Signatures::\n") - for _, file := range program.GetProgram().GetSourceFiles() { - if kind, ok := testingData.UpdatedSignatureKinds[file.Path()]; ok { - switch kind { - case incremental.SignatureUpdateKindComputedDts: - s.programBaselines.WriteString("(computed .d.ts) " + file.FileName() + "\n") - case incremental.SignatureUpdateKindStoredAtEmit: - s.programBaselines.WriteString("(stored at emit) " + file.FileName() + "\n") - case incremental.SignatureUpdateKindUsedVersion: - s.programBaselines.WriteString("(used version) " + file.FileName() + "\n") - } - } - } - - var filesWithoutIncludeReason []string - var fileNotInProgramWithIncludeReason []string - includeReasons := program.GetProgram().GetIncludeReasons() - for _, file := range program.GetProgram().GetSourceFiles() { - if _, ok := includeReasons[file.Path()]; !ok { - filesWithoutIncludeReason = append(filesWithoutIncludeReason, string(file.Path())) - } - } - for path := range includeReasons { - if program.GetProgram().GetSourceFileByPath(path) == nil && !program.GetProgram().IsMissingPath(path) { - fileNotInProgramWithIncludeReason = append(fileNotInProgramWithIncludeReason, string(path)) - } - } - if len(filesWithoutIncludeReason) > 0 || len(fileNotInProgramWithIncludeReason) > 0 { - s.writeHeaderToBaseline(&s.programIncludeBaselines, program) - s.programIncludeBaselines.WriteString("!!! Expected all files to have include reasons\nfilesWithoutIncludeReason::\n") - for _, file := range filesWithoutIncludeReason { - s.programIncludeBaselines.WriteString(" " + file + "\n") - } - s.programIncludeBaselines.WriteString("filesNotInProgramWithIncludeReason::\n") - for _, file := range fileNotInProgramWithIncludeReason { - s.programIncludeBaselines.WriteString(" " + file + "\n") - } - } -} - -func (s *testSys) baselinePrograms(baseline *strings.Builder, header string) string { - baseline.WriteString(s.programBaselines.String()) - s.programBaselines.Reset() - var result string - if s.programIncludeBaselines.Len() > 0 { - result += fmt.Sprintf("\n\n%s\n!!! Include reasons expectations don't match pls review!!!\n", header) - result += s.programIncludeBaselines.String() - s.programIncludeBaselines.Reset() - baseline.WriteString(result) - } - return result -} - -func (s *testSys) serializeState(baseline *strings.Builder) { - s.baselineOutput(baseline) - s.baselineFSwithDiff(baseline) - // todo watch - // this.serializeWatches(baseline); - // this.timeoutCallbacks.serialize(baseline); - // this.immediateCallbacks.serialize(baseline); - // this.pendingInstalls.serialize(baseline); - // this.service?.baseline(); -} - -var ( - fakeTimeStamp = "HH:MM:SS AM" - fakeDuration = "d.ddds" - - buildStartingAt = "build starting at " - buildFinishedIn = "build finished in " - listFileStart = "!!! List files start" - listFileEnd = "!!! List files end" - statisticsStart = "!!! Statistics start" - statisticsEnd = "!!! Statistics end" - buildStatusReportStart = "!!! Build Status Report Start" - buildStatusReportEnd = "!!! Build Status Report End" - watchStatusReportStart = "!!! Watch Status Report Start" - watchStatusReportEnd = "!!! Watch Status Report End" - traceStart = "!!! Trace start" - traceEnd = "!!! Trace end" -) - -func (s *testSys) baselineOutput(baseline io.Writer) { - fmt.Fprint(baseline, "\nOutput::\n") - output := s.getOutput(false) - fmt.Fprint(baseline, output) -} - -type outputSanitizer struct { - forComparing bool - lines []string - index int - outputLines []string -} - -func (o *outputSanitizer) addOutputLine(s string) { - if change := strings.ReplaceAll(s, fmt.Sprintf("'%s'", core.Version()), fmt.Sprintf("'%s'", harnessutil.FakeTSVersion)); change != s { - s = change - } - if change := strings.ReplaceAll(s, "Version "+core.Version(), "Version "+harnessutil.FakeTSVersion); change != s { - s = change - } - o.outputLines = append(o.outputLines, s) -} - -func (o *outputSanitizer) sanitizeBuildStatusTimeStamp() string { - statusLine := o.lines[o.index] - hhSeparator := strings.IndexRune(statusLine, ':') - if hhSeparator < 2 { - panic("Expected timestamp") - } - return statusLine[:hhSeparator-2] + fakeTimeStamp + statusLine[hhSeparator+len(fakeTimeStamp)-2:] -} - -func (o *outputSanitizer) transformLines() string { - for ; o.index < len(o.lines); o.index++ { - line := o.lines[o.index] - if strings.HasPrefix(line, buildStartingAt) { - if !o.forComparing { - o.addOutputLine(buildStartingAt + fakeTimeStamp) - } - continue - } - if strings.HasPrefix(line, buildFinishedIn) { - if !o.forComparing { - o.addOutputLine(buildFinishedIn + fakeDuration) - } - continue - } - if !o.addOrSkipLinesForComparing(listFileStart, listFileEnd, false, nil) && - !o.addOrSkipLinesForComparing(statisticsStart, statisticsEnd, true, nil) && - !o.addOrSkipLinesForComparing(traceStart, traceEnd, false, nil) && - !o.addOrSkipLinesForComparing(buildStatusReportStart, buildStatusReportEnd, false, o.sanitizeBuildStatusTimeStamp) && - !o.addOrSkipLinesForComparing(watchStatusReportStart, watchStatusReportEnd, false, o.sanitizeBuildStatusTimeStamp) { - o.addOutputLine(line) - } - } - return strings.Join(o.outputLines, "\n") -} - -func (o *outputSanitizer) addOrSkipLinesForComparing( - lineStart string, - lineEnd string, - skipEvenIfNotComparing bool, - sanitizeFirstLine func() string, -) bool { - if o.lines[o.index] != lineStart { - return false - } - o.index++ - isFirstLine := true - for ; o.index < len(o.lines); o.index++ { - if o.lines[o.index] == lineEnd { - return true - } - if !o.forComparing && !skipEvenIfNotComparing { - line := o.lines[o.index] - if isFirstLine && sanitizeFirstLine != nil { - line = sanitizeFirstLine() - isFirstLine = false - } - o.addOutputLine(line) - } - } - panic("Expected lineEnd" + lineEnd + " not found after " + lineStart) -} - -func (s *testSys) getOutput(forComparing bool) string { - lines := strings.Split(s.currentWrite.String(), "\n") - transformer := &outputSanitizer{ - forComparing: forComparing, - lines: lines, - outputLines: make([]string, 0, len(lines)), - } - return transformer.transformLines() -} - -func (s *testSys) clearOutput() { - s.currentWrite.Reset() - s.tracer.Reset() -} - -func (s *testSys) baselineFSwithDiff(baseline io.Writer) { - // todo: baselines the entire fs, possibly doesn't correctly diff all cases of emitted files, since emit isn't fully implemented and doesn't always emit the same way as strada - snap := map[string]*diffEntry{} - - diffs := map[string]string{} - - for path, file := range s.mapFs().Entries() { - if file.Mode&fs.ModeSymlink != 0 { - target, ok := s.mapFs().GetTargetOfSymlink(path) - if !ok { - panic("Failed to resolve symlink target: " + path) - } - newEntry := &diffEntry{symlinkTarget: target} - snap[path] = newEntry - s.addFsEntryDiff(diffs, newEntry, path) - continue - } else if file.Mode.IsRegular() { - newEntry := &diffEntry{content: string(file.Data), mTime: file.ModTime, isWritten: s.fs.writtenFiles.Has(path)} - snap[path] = newEntry - s.addFsEntryDiff(diffs, newEntry, path) - } - } - if s.serializedDiff != nil { - for path := range s.serializedDiff.snap { - if fileInfo := s.mapFs().GetFileInfo(path); fileInfo == nil { - // report deleted - s.addFsEntryDiff(diffs, nil, path) - } - } - } - var defaultLibs collections.SyncSet[string] - if s.fs.defaultLibs != nil { - s.fs.defaultLibs.Range(func(libPath string) bool { - defaultLibs.Add(libPath) - return true - }) - } - s.serializedDiff = &snapshot{ - snap: snap, - defaultLibs: &defaultLibs, - } - diffKeys := slices.Collect(maps.Keys(diffs)) - slices.Sort(diffKeys) - for _, path := range diffKeys { - fmt.Fprint(baseline, "//// ["+path+"] ", diffs[path], "\n") - } - fmt.Fprintln(baseline) - s.fs.writtenFiles = collections.SyncSet[string]{} // Reset written files after baseline -} - -func (s *testSys) addFsEntryDiff(diffs map[string]string, newDirContent *diffEntry, path string) { - var oldDirContent *diffEntry - var defaultLibs *collections.SyncSet[string] - if s.serializedDiff != nil { - oldDirContent = s.serializedDiff.snap[path] - defaultLibs = s.serializedDiff.defaultLibs - } - // todo handle more cases of fs changes - if oldDirContent == nil { - if s.fs.defaultLibs == nil || !s.fs.defaultLibs.Has(path) { - if newDirContent.symlinkTarget != "" { - diffs[path] = "-> " + newDirContent.symlinkTarget + " *new*" - } else { - diffs[path] = "*new* \n" + newDirContent.content - } - } - } else if newDirContent == nil { - diffs[path] = "*deleted*" - } else if newDirContent.content != oldDirContent.content { - diffs[path] = "*modified* \n" + newDirContent.content - } else if newDirContent.isWritten { - diffs[path] = "*rewrite with same content*" - } else if newDirContent.mTime != oldDirContent.mTime { - diffs[path] = "*mTime changed*" - } else if defaultLibs != nil && defaultLibs.Has(path) && s.fs.defaultLibs != nil && !s.fs.defaultLibs.Has(path) { - // Lib file that was read - diffs[path] = "*Lib*\n" + newDirContent.content - } -} - -func (s *testSys) writeFileNoError(path string, content string, writeByteOrderMark bool) { - if err := s.fsFromFileMap().WriteFile(path, content, writeByteOrderMark); err != nil { - panic(err) - } -} - -func (s *testSys) removeNoError(path string) { - if err := s.fsFromFileMap().Remove(path); err != nil { - panic(err) - } -} - -func (s *testSys) readFileNoError(path string) string { - content, ok := s.fsFromFileMap().ReadFile(path) - if !ok { - panic("File not found: " + path) - } - return content -} - -func (s *testSys) renameFileNoError(oldPath string, newPath string) { - s.writeFileNoError(newPath, s.readFileNoError(oldPath), false) - s.removeNoError(oldPath) -} - -func (s *testSys) replaceFileText(path string, oldText string, newText string) { - content := s.readFileNoError(path) - content = strings.Replace(content, oldText, newText, 1) - s.writeFileNoError(path, content, false) -} - -func (s *testSys) replaceFileTextAll(path string, oldText string, newText string) { - content := s.readFileNoError(path) - content = strings.ReplaceAll(content, oldText, newText) - s.writeFileNoError(path, content, false) -} - -func (s *testSys) appendFile(path string, text string) { - content := s.readFileNoError(path) - s.writeFileNoError(path, content+text, false) -} - -func (s *testSys) prependFile(path string, text string) { - content := s.readFileNoError(path) - s.writeFileNoError(path, text+content, false) -} diff --git a/kitcom/internal/tsgo/execute/tsctests/tsc_test.go b/kitcom/internal/tsgo/execute/tsctests/tsc_test.go deleted file mode 100644 index f72a8e6..0000000 --- a/kitcom/internal/tsgo/execute/tsctests/tsc_test.go +++ /dev/null @@ -1,3989 +0,0 @@ -package tsctests - -import ( - "fmt" - "slices" - "strings" - "testing" - - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/core" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/testutil/stringtestutil" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/vfs/vfstest" -) - -func TestTscCommandline(t *testing.T) { - t.Parallel() - testCases := []*tscInput{ - { - subScenario: "show help with ExitStatus.DiagnosticsPresent_OutputsSkipped", - env: map[string]string{ - "TS_TEST_TERMINAL_WIDTH": "120", - }, - commandLineArgs: nil, - }, - { - subScenario: "show help with ExitStatus.DiagnosticsPresent_OutputsSkipped when host cannot provide terminal width", - commandLineArgs: nil, - }, - { - subScenario: "does not add color when NO_COLOR is set", - env: map[string]string{ - "NO_COLOR": "true", - }, - commandLineArgs: nil, - }, - { - subScenario: "when build not first argument", - commandLineArgs: []string{"--verbose", "--build"}, - }, - { - subScenario: "help", - commandLineArgs: []string{"--help"}, - }, - { - subScenario: "help all", - commandLineArgs: []string{"--help", "--all"}, - }, - { - subScenario: "Parse --lib option with file name", - files: FileMap{"/home/src/workspaces/project/first.ts": `export const Key = Symbol()`}, - commandLineArgs: []string{"--lib", "es6 ", "first.ts"}, - }, - { - subScenario: "Project is empty string", - files: FileMap{ - "/home/src/workspaces/project/first.ts": `export const a = 1`, - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "strict": true, - "noEmit": true - } - }`), - }, - commandLineArgs: []string{}, - }, - { - subScenario: "Parse -p", - files: FileMap{ - "/home/src/workspaces/project/first.ts": `export const a = 1`, - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "strict": true, - "noEmit": true - } - }`), - }, - commandLineArgs: []string{"-p", "."}, - }, - { - subScenario: "Parse -p with path to tsconfig file", - files: FileMap{ - "/home/src/workspaces/project/first.ts": `export const a = 1`, - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "strict": true, - "noEmit": true - } - }`), - }, - commandLineArgs: []string{"-p", "/home/src/workspaces/project/tsconfig.json"}, - }, - { - subScenario: "Parse -p with path to tsconfig folder", - files: FileMap{ - "/home/src/workspaces/project/first.ts": `export const a = 1`, - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "strict": true, - "noEmit": true - } - }`), - }, - commandLineArgs: []string{"-p", "/home/src/workspaces/project"}, - }, - { - subScenario: "Parse enum type options", - commandLineArgs: []string{"--moduleResolution", "nodenext ", "first.ts", "--module", "nodenext", "--target", "esnext", "--moduleDetection", "auto", "--jsx", "react", "--newLine", "crlf"}, - }, - { - subScenario: "Parse watch interval option", - files: FileMap{ - "/home/src/workspaces/project/first.ts": `export const a = 1`, - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "strict": true, - "noEmit": true - } - }`), - }, - commandLineArgs: []string{"-w", "--watchInterval", "1000"}, - }, - { - subScenario: "Parse watch interval option without tsconfig.json", - commandLineArgs: []string{"-w", "--watchInterval", "1000"}, - }, - { - subScenario: "Config with references and empty file and refers to config with noEmit", - files: FileMap{ - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(`{ - "files": [], - "references": [ - { - "path": "./packages/pkg1" - }, - ], - }`), - "/home/src/workspaces/project/packages/pkg1/tsconfig.json": stringtestutil.Dedent(`{ - "compilerOptions": { - "composite": true, - "noEmit": true - }, - "files": [ - "./index.ts", - ], - }`), - "/home/src/workspaces/project/packages/pkg1/index.ts": `export const a = 1;`, - }, - commandLineArgs: []string{"-p", "."}, - }, - } - - for _, testCase := range testCases { - testCase.run(t, "commandLine") - } -} - -func TestTscComposite(t *testing.T) { - t.Parallel() - testCases := []*tscInput{ - { - subScenario: "when setting composite false on command line", - files: FileMap{ - "/home/src/workspaces/project/src/main.ts": "export const x = 10;", - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "target": "es5", - "module": "commonjs", - "composite": true, - }, - "include": [ - "src/**/*.ts", - ], - }`), - }, - commandLineArgs: []string{"--composite", "false"}, - }, - { - // !!! sheetal null is not reflected in final options - subScenario: "when setting composite null on command line", - files: FileMap{ - "/home/src/workspaces/project/src/main.ts": "export const x = 10;", - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "target": "es5", - "module": "commonjs", - "composite": true, - }, - "include": [ - "src/**/*.ts", - ], - }`), - }, - commandLineArgs: []string{"--composite", "null"}, - }, - { - subScenario: "when setting composite false on command line but has tsbuild info in config", - files: FileMap{ - "/home/src/workspaces/project/src/main.ts": "export const x = 10;", - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "target": "es5", - "module": "commonjs", - "composite": true, - "tsBuildInfoFile": "tsconfig.json.tsbuildinfo", - }, - "include": [ - "src/**/*.ts", - ], - }`), - }, - commandLineArgs: []string{"--composite", "false"}, - }, - { - subScenario: "when setting composite false and tsbuildinfo as null on command line but has tsbuild info in config", - files: FileMap{ - "/home/src/workspaces/project/src/main.ts": "export const x = 10;", - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "target": "es5", - "module": "commonjs", - "composite": true, - "tsBuildInfoFile": "tsconfig.json.tsbuildinfo", - }, - "include": [ - "src/**/*.ts", - ], - }`), - }, - commandLineArgs: []string{"--composite", "false", "--tsBuildInfoFile", "null"}, - }, - { - subScenario: "converting to modules", - files: FileMap{ - "/home/src/workspaces/project/src/main.ts": "const x = 10;", - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "module": "none", - "composite": true, - }, - }`), - }, - edits: []*tscEdit{ - { - caption: "convert to modules", - edit: func(sys *testSys) { - sys.replaceFileText("/home/src/workspaces/project/tsconfig.json", "none", "es2015") - }, - }, - }, - }, - { - subScenario: "synthetic jsx import of ESM module from CJS module no crash no jsx element", - files: FileMap{ - "/home/src/projects/project/src/main.ts": "export default 42;", - "/home/src/projects/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "module": "Node16", - "jsx": "react-jsx", - "jsxImportSource": "solid-js", - }, - }`), - "/home/src/projects/project/node_modules/solid-js/package.json": stringtestutil.Dedent(` - { - "name": "solid-js", - "type": "module" - } - `), - "/home/src/projects/project/node_modules/solid-js/jsx-runtime.d.ts": stringtestutil.Dedent(` - export namespace JSX { - type IntrinsicElements = { div: {}; }; - } - `), - }, - cwd: "/home/src/projects/project", - }, - { - subScenario: "synthetic jsx import of ESM module from CJS module error on jsx element", - files: FileMap{ - "/home/src/projects/project/src/main.tsx": "export default
;", - "/home/src/projects/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "module": "Node16", - "jsx": "react-jsx", - "jsxImportSource": "solid-js", - }, - }`), - "/home/src/projects/project/node_modules/solid-js/package.json": stringtestutil.Dedent(` - { - "name": "solid-js", - "type": "module" - } - `), - "/home/src/projects/project/node_modules/solid-js/jsx-runtime.d.ts": stringtestutil.Dedent(` - export namespace JSX { - type IntrinsicElements = { div: {}; }; - } - `), - }, - cwd: "/home/src/projects/project", - }, - } - - for _, testCase := range testCases { - testCase.run(t, "composite") - } -} - -func TestTscDeclarationEmit(t *testing.T) { - t.Parallel() - getBuildDeclarationEmitDtsReferenceAsTrippleSlashMap := func(useNoRef bool) FileMap { - files := FileMap{ - "/home/src/workspaces/solution/tsconfig.base.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "rootDir": "./", - "outDir": "lib", - }, - }`), - "/home/src/workspaces/solution/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { "composite": true }, - "references": [{ "path": "./src" }], - "include": [], - }`), - "/home/src/workspaces/solution/src/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { "composite": true }, - "references": [{ "path": "./subProject" }, { "path": "./subProject2" }], - "include": [], - }`), - "/home/src/workspaces/solution/src/subProject/tsconfig.json": stringtestutil.Dedent(` - { - "extends": "../../tsconfig.base.json", - "compilerOptions": { "composite": true }, - "references": [{ "path": "../common" }], - "include": ["./index.ts"], - }`), - "/home/src/workspaces/solution/src/subProject/index.ts": stringtestutil.Dedent(` - import { Nominal } from '../common/nominal'; - export type MyNominal = Nominal;`), - "/home/src/workspaces/solution/src/subProject2/tsconfig.json": stringtestutil.Dedent(` - { - "extends": "../../tsconfig.base.json", - "compilerOptions": { "composite": true }, - "references": [{ "path": "../subProject" }], - "include": ["./index.ts"], - }`), - "/home/src/workspaces/solution/src/subProject2/index.ts": stringtestutil.Dedent(` - import { MyNominal } from '../subProject/index'; - const variable = { - key: 'value' as MyNominal, - }; - export function getVar(): keyof typeof variable { - return 'key'; - }`), - "/home/src/workspaces/solution/src/common/tsconfig.json": stringtestutil.Dedent(` - { - "extends": "../../tsconfig.base.json", - "compilerOptions": { "composite": true }, - "include": ["./nominal.ts"], - }`), - "/home/src/workspaces/solution/src/common/nominal.ts": stringtestutil.Dedent(` - /// - export declare type Nominal = MyNominal;`), - "/home/src/workspaces/solution/src/common/types.d.ts": stringtestutil.Dedent(` - declare type MyNominal = T & { - specialKey: Name; - };`), - } - if useNoRef { - files["/home/src/workspaces/solution/tsconfig.json"] = stringtestutil.Dedent(` - { - "extends": "./tsconfig.base.json", - "compilerOptions": { "composite": true }, - "include": ["./src/**/*.ts"], - }`) - } - return files - } - - getTscDeclarationEmitDtsErrorsFileMap := func(composite bool, incremental bool) FileMap { - return FileMap{ - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(fmt.Sprintf(` - { - "compilerOptions": { - "module": "NodeNext", - "moduleResolution": "NodeNext", - "composite": %t, - "incremental": %t, - "declaration": true, - "skipLibCheck": true, - "skipDefaultLibCheck": true, - }, - }`, composite, incremental)), - "/home/src/workspaces/project/index.ts": stringtestutil.Dedent(` - import ky from 'ky'; - export const api = ky.extend({}); - `), - "/home/src/workspaces/project/package.json": stringtestutil.Dedent(` - { - "type": "module" - }`), - "/home/src/workspaces/project/node_modules/ky/distribution/index.d.ts": stringtestutil.Dedent(` - type KyInstance = { - extend(options: Record): KyInstance; - } - declare const ky: KyInstance; - export default ky; - `), - "/home/src/workspaces/project/node_modules/ky/package.json": stringtestutil.Dedent(` - { - "name": "ky", - "type": "module", - "main": "./distribution/index.js" - } - `), - } - } - - pluginOneConfig := func() string { - return stringtestutil.Dedent(` - { - "compilerOptions": { - "target": "es5", - "declaration": true, - "traceResolution": true, - }, - }`) - } - - pluginOneIndex := func() string { - return `import pluginTwo from "plugin-two"; // include this to add reference to symlink` - } - - pluginOneAction := func() string { - return stringtestutil.Dedent(` - import { actionCreatorFactory } from "typescript-fsa"; // Include version of shared lib - const action = actionCreatorFactory("somekey"); - const featureOne = action<{ route: string }>("feature-one"); - export const actions = { featureOne };`) - } - - pluginTwoDts := func() string { - return stringtestutil.Dedent(` - declare const _default: { - features: { - featureOne: { - actions: { - featureOne: { - (payload: { - name: string; - order: number; - }, meta?: { - [key: string]: any; - }): import("typescript-fsa").Action<{ - name: string; - order: number; - }>; - }; - }; - path: string; - }; - }; - }; - export default _default;`) - } - - fsaPackageJson := func() string { - return stringtestutil.Dedent(` - { - "name": "typescript-fsa", - "version": "3.0.0-beta-2" - }`) - } - - fsaIndex := func() string { - return stringtestutil.Dedent(` - export interface Action { - type: string; - payload: Payload; - } - export declare type ActionCreator = { - type: string; - (payload: Payload): Action; - } - export interface ActionCreatorFactory { - (type: string): ActionCreator; - } - export declare function actionCreatorFactory(prefix?: string | null): ActionCreatorFactory; - export default actionCreatorFactory;`) - } - testCases := []*tscInput{ - { - subScenario: "when declaration file is referenced through triple slash", - files: getBuildDeclarationEmitDtsReferenceAsTrippleSlashMap(false), - cwd: "/home/src/workspaces/solution", - commandLineArgs: []string{"--b", "--verbose"}, - }, - { - subScenario: "when declaration file is referenced through triple slash but uses no references", - files: getBuildDeclarationEmitDtsReferenceAsTrippleSlashMap(true), - cwd: "/home/src/workspaces/solution", - commandLineArgs: []string{"--b", "--verbose"}, - }, - { - subScenario: "when declaration file used inferred type from referenced project", - files: FileMap{ - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "paths": { "@fluentui/*": ["./packages/*/src"] }, - }, - }`), - "/home/src/workspaces/project/packages/pkg1/src/index.ts": stringtestutil.Dedent(` - export interface IThing { - a: string; - } - export interface IThings { - thing1: IThing; - } - `), - "/home/src/workspaces/project/packages/pkg1/tsconfig.json": stringtestutil.Dedent(` - { - "extends": "../../tsconfig", - "compilerOptions": { "outDir": "lib" }, - "include": ["src"], - } - `), - "/home/src/workspaces/project/packages/pkg2/src/index.ts": stringtestutil.Dedent(` - import { IThings } from '@fluentui/pkg1'; - export function fn4() { - const a: IThings = { thing1: { a: 'b' } }; - return a.thing1; - } - `), - "/home/src/workspaces/project/packages/pkg2/tsconfig.json": stringtestutil.Dedent(` - { - "extends": "../../tsconfig", - "compilerOptions": { "outDir": "lib" }, - "include": ["src"], - "references": [{ "path": "../pkg1" }], - } - `), - }, - commandLineArgs: []string{"--b", "packages/pkg2/tsconfig.json", "--verbose"}, - }, - { - subScenario: "reports dts generation errors", - files: getTscDeclarationEmitDtsErrorsFileMap(false, false), - commandLineArgs: []string{"-b", "--explainFiles", "--listEmittedFiles", "--v"}, - edits: noChangeOnlyEdit, - }, - { - subScenario: "reports dts generation errors with incremental", - files: getTscDeclarationEmitDtsErrorsFileMap(false, true), - commandLineArgs: []string{"-b", "--explainFiles", "--listEmittedFiles", "--v"}, - edits: noChangeOnlyEdit, - }, - { - subScenario: "reports dts generation errors", - files: getTscDeclarationEmitDtsErrorsFileMap(false, false), - commandLineArgs: []string{"--explainFiles", "--listEmittedFiles"}, - edits: []*tscEdit{ - noChange, - { - caption: "build -b", - commandLineArgs: []string{"-b", "--explainFiles", "--listEmittedFiles", "--v"}, - }, - }, - }, - { - subScenario: "reports dts generation errors with incremental", - files: getTscDeclarationEmitDtsErrorsFileMap(true, true), - commandLineArgs: []string{"--explainFiles", "--listEmittedFiles"}, - edits: []*tscEdit{ - noChange, - { - caption: "build -b", - commandLineArgs: []string{"-b", "--explainFiles", "--listEmittedFiles", "--v"}, - }, - }, - }, - { - subScenario: "when using Windows paths and uppercase letters", - files: FileMap{ - "D:/Work/pkg1/package.json": stringtestutil.Dedent(` - { - "name": "ts-specifier-bug", - "version": "1.0.0", - "main": "index.js" - }`), - "D:/Work/pkg1/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "declaration": true, - "target": "es2017", - "outDir": "./dist", - }, - "include": ["src"], - }`), - "D:/Work/pkg1/src/main.ts": stringtestutil.Dedent(` - import { PartialType } from './utils'; - - class Common {} - - export class Sub extends PartialType(Common) { - id: string; - } - `), - "D:/Work/pkg1/src/utils/index.ts": stringtestutil.Dedent(` - import { MyType, MyReturnType } from './type-helpers'; - - export function PartialType(classRef: MyType) { - abstract class PartialClassType { - constructor() {} - } - - return PartialClassType as MyReturnType; - } - `), - "D:/Work/pkg1/src/utils/type-helpers.ts": stringtestutil.Dedent(` - export type MyReturnType = { - new (...args: any[]): any; - }; - - export interface MyType extends Function { - new (...args: any[]): T; - } - `), - }, - cwd: "D:/Work/pkg1", - windowsStyleRoot: "D:/", - ignoreCase: true, - commandLineArgs: []string{"-p", "D:\\Work\\pkg1", "--explainFiles"}, - }, - { - // !!! sheetal redirected files not yet implemented - subScenario: "when same version is referenced through source and another symlinked package", - files: FileMap{ - `/user/username/projects/myproject/plugin-two/index.d.ts`: pluginTwoDts(), - `/user/username/projects/myproject/plugin-two/node_modules/typescript-fsa/package.json`: fsaPackageJson(), - `/user/username/projects/myproject/plugin-two/node_modules/typescript-fsa/index.d.ts`: fsaIndex(), - `/user/username/projects/myproject/plugin-one/tsconfig.json`: pluginOneConfig(), - `/user/username/projects/myproject/plugin-one/index.ts`: pluginOneIndex(), - `/user/username/projects/myproject/plugin-one/action.ts`: pluginOneAction(), - `/user/username/projects/myproject/plugin-one/node_modules/typescript-fsa/package.json`: fsaPackageJson(), - `/user/username/projects/myproject/plugin-one/node_modules/typescript-fsa/index.d.ts`: fsaIndex(), - `/user/username/projects/myproject/plugin-one/node_modules/plugin-two`: vfstest.Symlink(`/user/username/projects/myproject/plugin-two`), - }, - cwd: "/user/username/projects/myproject", - commandLineArgs: []string{"-p", "plugin-one", "--explainFiles"}, - }, - { - // !!! sheetal redirected files not yet implemented - subScenario: "when same version is referenced through source and another symlinked package with indirect link", - files: FileMap{ - `/user/username/projects/myproject/plugin-two/package.json`: stringtestutil.Dedent(` - { - "name": "plugin-two", - "version": "0.1.3", - "main": "dist/commonjs/index.js" - }`), - `/user/username/projects/myproject/plugin-two/dist/commonjs/index.d.ts`: pluginTwoDts(), - `/user/username/projects/myproject/plugin-two/node_modules/typescript-fsa/package.json`: fsaPackageJson(), - `/user/username/projects/myproject/plugin-two/node_modules/typescript-fsa/index.d.ts`: fsaIndex(), - `/user/username/projects/myproject/plugin-one/tsconfig.json`: pluginOneConfig(), - `/user/username/projects/myproject/plugin-one/index.ts`: pluginOneIndex() + "\n" + pluginOneAction(), - `/user/username/projects/myproject/plugin-one/node_modules/typescript-fsa/package.json`: fsaPackageJson(), - `/user/username/projects/myproject/plugin-one/node_modules/typescript-fsa/index.d.ts`: fsaIndex(), - `/temp/yarn/data/link/plugin-two`: vfstest.Symlink(`/user/username/projects/myproject/plugin-two`), - `/user/username/projects/myproject/plugin-one/node_modules/plugin-two`: vfstest.Symlink(`/temp/yarn/data/link/plugin-two`), - }, - cwd: "/user/username/projects/myproject", - commandLineArgs: []string{"-p", "plugin-one", "--explainFiles"}, - }, - { - // !!! sheetal strada has error for d.ts generation in pkg3/src/keys.ts but corsa doesnt have that - subScenario: "when pkg references sibling package through indirect symlink", - files: FileMap{ - `/user/username/projects/myproject/pkg1/dist/index.d.ts`: `export * from './types';`, - `/user/username/projects/myproject/pkg1/dist/types.d.ts`: stringtestutil.Dedent(` - export declare type A = { - id: string; - }; - export declare type B = { - id: number; - }; - export declare type IdType = A | B; - export declare class MetadataAccessor { - readonly key: string; - private constructor(); - toString(): string; - static create(key: string): MetadataAccessor; - }`), - `/user/username/projects/myproject/pkg1/package.json`: stringtestutil.Dedent(` - { - "name": "@raymondfeng/pkg1", - "version": "1.0.0", - "main": "dist/index.js", - "typings": "dist/index.d.ts" - }`), - `/user/username/projects/myproject/pkg2/dist/index.d.ts`: `export * from './types';`, - `/user/username/projects/myproject/pkg2/dist/types.d.ts`: `export {MetadataAccessor} from '@raymondfeng/pkg1';`, - `/user/username/projects/myproject/pkg2/package.json`: stringtestutil.Dedent(` - { - "name": "@raymondfeng/pkg2", - "version": "1.0.0", - "main": "dist/index.js", - "typings": "dist/index.d.ts" - }`), - `/user/username/projects/myproject/pkg3/src/index.ts`: `export * from './keys';`, - `/user/username/projects/myproject/pkg3/src/keys.ts`: stringtestutil.Dedent(` - import {MetadataAccessor} from "@raymondfeng/pkg2"; - export const ADMIN = MetadataAccessor.create('1');`), - `/user/username/projects/myproject/pkg3/tsconfig.json`: stringtestutil.Dedent(` - { - "compilerOptions": { - "outDir": "dist", - "rootDir": "src", - "target": "es5", - "module": "commonjs", - "strict": true, - "esModuleInterop": true, - "declaration": true, - }, - }`), - `/user/username/projects/myproject/pkg2/node_modules/@raymondfeng/pkg1`: vfstest.Symlink(`/user/username/projects/myproject/pkg1`), - `/user/username/projects/myproject/pkg3/node_modules/@raymondfeng/pkg2`: vfstest.Symlink(`/user/username/projects/myproject/pkg2`), - }, - cwd: "/user/username/projects/myproject", - commandLineArgs: []string{"-p", "pkg3", "--explainFiles"}, - }, - } - - for _, test := range testCases { - test.run(t, "declarationEmit") - } -} - -func TestTscExtends(t *testing.T) { - t.Parallel() - getBuildConfigFileExtendsFileMap := func() FileMap { - return FileMap{ - "/home/src/workspaces/solution/tsconfig.json": stringtestutil.Dedent(` - { - "references": [ - { "path": "./shared/tsconfig.json" }, - { "path": "./webpack/tsconfig.json" }, - ], - "files": [], - }`), - "/home/src/workspaces/solution/shared/tsconfig-base.json": stringtestutil.Dedent(` - { - "include": ["./typings-base/"], - }`), - "/home/src/workspaces/solution/shared/typings-base/globals.d.ts": `type Unrestricted = any;`, - "/home/src/workspaces/solution/shared/tsconfig.json": stringtestutil.Dedent(` - { - "extends": "./tsconfig-base.json", - "compilerOptions": { - "composite": true, - "outDir": "../target-tsc-build/", - "rootDir": "..", - }, - "files": ["./index.ts"], - }`), - "/home/src/workspaces/solution/shared/index.ts": `export const a: Unrestricted = 1;`, - "/home/src/workspaces/solution/webpack/tsconfig.json": stringtestutil.Dedent(` - { - "extends": "../shared/tsconfig-base.json", - "compilerOptions": { - "composite": true, - "outDir": "../target-tsc-build/", - "rootDir": "..", - }, - "files": ["./index.ts"], - "references": [{ "path": "../shared/tsconfig.json" }], - }`), - "/home/src/workspaces/solution/webpack/index.ts": `export const b: Unrestricted = 1;`, - } - } - getTscExtendsWithSymlinkTestCase := func(builtType string) *tscInput { - return &tscInput{ - subScenario: "resolves the symlink path", - files: FileMap{ - "/users/user/projects/myconfigs/node_modules/@something/tsconfig-node/tsconfig.json": stringtestutil.Dedent(` - { - "extends": "@something/tsconfig-base/tsconfig.json", - "compilerOptions": { - "removeComments": true - } - } - `), - "/users/user/projects/myconfigs/node_modules/@something/tsconfig-base/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { "composite": true } - } - `), - "/users/user/projects/myproject/src/index.ts": stringtestutil.Dedent(` - // some comment - export const x = 10; - `), - "/users/user/projects/myproject/src/tsconfig.json": stringtestutil.Dedent(` - { - "extends": "@something/tsconfig-node/tsconfig.json" - }`), - "/users/user/projects/myproject/node_modules/@something/tsconfig-node": vfstest.Symlink("/users/user/projects/myconfigs/node_modules/@something/tsconfig-node"), - }, - cwd: "/users/user/projects/myproject", - commandLineArgs: []string{builtType, "src", "--extendedDiagnostics"}, - } - } - getTscExtendsConfigDirTestCase := func(subScenarioSufix string, commandLineArgs []string, edits []*tscEdit) *tscInput { - return &tscInput{ - subScenario: "configDir template" + subScenarioSufix, - files: FileMap{ - "/home/src/projects/configs/first/tsconfig.json": stringtestutil.Dedent(` - { - "extends": "../second/tsconfig.json", - "include": ["${configDir}/src"], - "compilerOptions": { - "typeRoots": ["root1", "${configDir}/root2", "root3"], - "types": [], - }, - }`), - "/home/src/projects/configs/second/tsconfig.json": stringtestutil.Dedent(` - { - "files": ["${configDir}/main.ts"], - "compilerOptions": { - "declarationDir": "${configDir}/decls", - "paths": { - "@myscope/*": ["${configDir}/types/*"], - }, - }, - "watchOptions": { - "excludeFiles": ["${configDir}/main.ts"], - }, - }`), - "/home/src/projects/myproject/tsconfig.json": stringtestutil.Dedent(` - { - "extends": "../configs/first/tsconfig.json", - "compilerOptions": { - "declaration": true, - "outDir": "outDir", - "traceResolution": true, - }, - }`), - "/home/src/projects/myproject/main.ts": stringtestutil.Dedent(` - // some comment - export const y = 10; - import { x } from "@myscope/sometype"; - `), - "/home/src/projects/myproject/types/sometype.ts": stringtestutil.Dedent(` - export const x = 10; - `), - }, - cwd: "/home/src/projects/myproject", - commandLineArgs: commandLineArgs, - edits: edits, - } - } - testCases := []*tscInput{ - { - subScenario: "when building solution with projects extends config with include", - files: getBuildConfigFileExtendsFileMap(), - cwd: "/home/src/workspaces/solution", - commandLineArgs: []string{"--b", "--v", "--listFiles"}, - }, - { - subScenario: "when building project uses reference and both extend config with include", - files: getBuildConfigFileExtendsFileMap(), - cwd: "/home/src/workspaces/solution", - commandLineArgs: []string{"--b", "webpack/tsconfig.json", "--v", "--listFiles"}, - }, - getTscExtendsWithSymlinkTestCase("-p"), - getTscExtendsWithSymlinkTestCase("-b"), - getTscExtendsConfigDirTestCase("", []string{"--explainFiles"}, nil), - getTscExtendsConfigDirTestCase(" showConfig", []string{"--showConfig"}, nil), - getTscExtendsConfigDirTestCase(" with commandline", []string{"--explainFiles", "--outDir", "${configDir}/outDir"}, nil), - getTscExtendsConfigDirTestCase("", []string{"--b", "--explainFiles", "--v"}, nil), - getTscExtendsConfigDirTestCase("", []string{"--b", "-w", "--explainFiles", "--v"}, []*tscEdit{ - { - caption: "edit extended config file", - edit: func(sys *testSys) { - sys.writeFileNoError( - "/home/src/projects/configs/first/tsconfig.json", - stringtestutil.Dedent(` - { - "extends": "../second/tsconfig.json", - "include": ["${configDir}/src"], - "compilerOptions": { - "typeRoots": ["${configDir}/root2"], - "types": [], - }, - }`), - false, - ) - }, - }, - }), - } - - for _, test := range testCases { - test.run(t, "extends") - } -} - -func TestTscIncremental(t *testing.T) { - t.Parallel() - getConstEnumTest := func(bdsContents string, changeEnumFile string, testSuffix string) *tscInput { - return &tscInput{ - subScenario: "const enums" + testSuffix, - files: FileMap{ - "/home/src/workspaces/project/a.ts": stringtestutil.Dedent(` - import {A} from "./c" - let a = A.ONE - `), - "/home/src/workspaces/project/b.d.ts": stringtestutil.Dedent(bdsContents), - "/home/src/workspaces/project/c.ts": stringtestutil.Dedent(` - import {A} from "./b" - let b = A.ONE - export {A} - `), - "/home/src/workspaces/project/worker.d.ts": stringtestutil.Dedent(` - export const enum AWorker { - ONE = 1 - } - `), - }, - commandLineArgs: []string{"-i", `a.ts`, "--tsbuildinfofile", "a.tsbuildinfo"}, - edits: []*tscEdit{ - { - caption: "change enum value", - edit: func(sys *testSys) { - sys.replaceFileText(changeEnumFile, "1", "2") - }, - }, - { - caption: "change enum value again", - edit: func(sys *testSys) { - sys.replaceFileText(changeEnumFile, "2", "3") - }, - }, - { - caption: "something else changes in b.d.ts", - edit: func(sys *testSys) { - sys.appendFile("/home/src/workspaces/project/b.d.ts", "export const randomThing = 10;") - }, - }, - { - caption: "something else changes in b.d.ts again", - edit: func(sys *testSys) { - sys.appendFile("/home/src/workspaces/project/b.d.ts", "export const randomThing2 = 10;") - }, - }, - }, - } - } - testCases := []*tscInput{ - { - subScenario: "serializing error chain", - files: FileMap{ - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "incremental": true, - "strict": true, - "jsx": "react", - "module": "esnext", - }, - }`), - "/home/src/workspaces/project/index.tsx": stringtestutil.Dedent(` - declare namespace JSX { - interface ElementChildrenAttribute { children: {}; } - interface IntrinsicElements { div: {} } - } - - declare var React: any; - - declare function Component(props: never): any; - declare function Component(props: { children?: number }): any; - ( -
-
- )`), - }, - edits: noChangeOnlyEdit, - }, - { - subScenario: "serializing composite project", - files: FileMap{ - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "strict": true, - "module": "esnext", - }, - }`), - "/home/src/workspaces/project/index.tsx": `export const a = 1;`, - "/home/src/workspaces/project/other.ts": `export const b = 2;`, - }, - }, - { - subScenario: "change to modifier of class expression field with declaration emit enabled", - files: FileMap{ - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "module": "esnext", - "declaration": true - } - }`), - "/home/src/workspaces/project/main.ts": stringtestutil.Dedent(` - import MessageablePerson from './MessageablePerson.js'; - function logMessage( person: MessageablePerson ) { - console.log( person.message ); - }`), - "/home/src/workspaces/project/MessageablePerson.ts": stringtestutil.Dedent(` - const Messageable = () => { - return class MessageableClass { - public message = 'hello'; - } - }; - const wrapper = () => Messageable(); - type MessageablePerson = InstanceType>; - export default MessageablePerson;`), - tscLibPath + "/lib.d.ts": tscDefaultLibContent + "\n" + stringtestutil.Dedent(` - type ReturnType any> = T extends (...args: any) => infer R ? R : any; - type InstanceType any> = T extends abstract new (...args: any) => infer R ? R : any;`), - }, - commandLineArgs: []string{"--incremental"}, - edits: []*tscEdit{ - noChange, - { - caption: "modify public to protected", - edit: func(sys *testSys) { - sys.replaceFileText("/home/src/workspaces/project/MessageablePerson.ts", "public", "protected") - }, - }, - noChange, - { - caption: "modify protected to public", - edit: func(sys *testSys) { - sys.replaceFileText("/home/src/workspaces/project/MessageablePerson.ts", "protected", "public") - }, - }, - noChange, - }, - }, - { - subScenario: "change to modifier of class expression field", - files: FileMap{ - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "module": "esnext" - } - }`), - "/home/src/workspaces/project/main.ts": stringtestutil.Dedent(` - import MessageablePerson from './MessageablePerson.js'; - function logMessage( person: MessageablePerson ) { - console.log( person.message ); - }`), - "/home/src/workspaces/project/MessageablePerson.ts": stringtestutil.Dedent(` - const Messageable = () => { - return class MessageableClass { - public message = 'hello'; - } - }; - const wrapper = () => Messageable(); - type MessageablePerson = InstanceType>; - export default MessageablePerson;`), - tscLibPath + "/lib.d.ts": tscDefaultLibContent + "\n" + stringtestutil.Dedent(` - type ReturnType any> = T extends (...args: any) => infer R ? R : any; - type InstanceType any> = T extends abstract new (...args: any) => infer R ? R : any;`), - }, - commandLineArgs: []string{"--incremental"}, - edits: []*tscEdit{ - noChange, - { - caption: "modify public to protected", - edit: func(sys *testSys) { - sys.replaceFileText("/home/src/workspaces/project/MessageablePerson.ts", "public", "protected") - }, - }, - noChange, - { - caption: "modify protected to public", - edit: func(sys *testSys) { - sys.replaceFileText("/home/src/workspaces/project/MessageablePerson.ts", "protected", "public") - }, - }, - noChange, - }, - }, - { - subScenario: "when passing filename for buildinfo on commandline", - files: FileMap{ - "/home/src/workspaces/project/src/main.ts": "export const x = 10;", - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "target": "es5", - "module": "commonjs" - }, - "include": [ - "src/**/*.ts" - ], - }`), - }, - commandLineArgs: []string{"--incremental", "--tsBuildInfoFile", ".tsbuildinfo", "--explainFiles"}, - edits: noChangeOnlyEdit, - }, - { - subScenario: "when passing rootDir from commandline", - files: FileMap{ - "/home/src/workspaces/project/src/main.ts": "export const x = 10;", - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "incremental": true, - "outDir": "dist" - } - }`), - }, - commandLineArgs: []string{"--rootDir", "src"}, - edits: noChangeOnlyEdit, - }, - { - subScenario: "with only dts files", - files: FileMap{ - "/home/src/workspaces/project/src/main.d.ts": "export const x = 10;", - "/home/src/workspaces/project/src/another.d.ts": "export const y = 10;", - "/home/src/workspaces/project/tsconfig.json": "{}", - }, - commandLineArgs: []string{"--incremental"}, - edits: []*tscEdit{ - noChange, - { - caption: "modify d.ts file", - edit: func(sys *testSys) { - sys.appendFile("/home/src/workspaces/project/src/main.d.ts", "export const xy = 100;") - }, - }, - }, - }, - { - subScenario: "when passing rootDir is in the tsconfig", - files: FileMap{ - "/home/src/workspaces/project/src/main.ts": "export const x = 10;", - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "incremental": true, - "outDir": "dist", - "rootDir": "./" - } - }`), - }, - edits: noChangeOnlyEdit, - }, - { - subScenario: "tsbuildinfo has error", - files: FileMap{ - "/home/src/workspaces/project/main.ts": "export const x = 10;", - "/home/src/workspaces/project/tsconfig.json": "{}", - "/home/src/workspaces/project/tsconfig.tsbuildinfo": "Some random string", - }, - commandLineArgs: []string{"-i"}, - edits: []*tscEdit{ - { - caption: "tsbuildinfo written has error", - edit: func(sys *testSys) { - sys.prependFile("/home/src/workspaces/project/tsconfig.tsbuildinfo", "Some random string") - }, - }, - }, - }, - { - subScenario: "when global file is added, the signatures are updated", - files: FileMap{ - "/home/src/workspaces/project/src/main.ts": stringtestutil.Dedent(` - /// - /// - function main() { } - `), - "/home/src/workspaces/project/src/anotherFileWithSameReferenes.ts": stringtestutil.Dedent(` - /// - /// - function anotherFileWithSameReferenes() { } - `), - "/home/src/workspaces/project/src/filePresent.ts": `function something() { return 10; }`, - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { "composite": true }, - "include": ["src/**/*.ts"], - }`), - }, - commandLineArgs: []string{}, - edits: []*tscEdit{ - noChange, - { - caption: "Modify main file", - edit: func(sys *testSys) { - sys.appendFile(`/home/src/workspaces/project/src/main.ts`, `something();`) - }, - }, - { - caption: "Modify main file again", - edit: func(sys *testSys) { - sys.appendFile(`/home/src/workspaces/project/src/main.ts`, `something();`) - }, - }, - { - caption: "Add new file and update main file", - edit: func(sys *testSys) { - sys.writeFileNoError(`/home/src/workspaces/project/src/newFile.ts`, "function foo() { return 20; }", false) - sys.prependFile( - `/home/src/workspaces/project/src/main.ts`, - `/// -`, - ) - sys.appendFile(`/home/src/workspaces/project/src/main.ts`, `foo();`) - }, - }, - { - caption: "Write file that could not be resolved", - edit: func(sys *testSys) { - sys.writeFileNoError(`/home/src/workspaces/project/src/fileNotFound.ts`, "function something2() { return 20; }", false) - }, - }, - { - caption: "Modify main file", - edit: func(sys *testSys) { - sys.appendFile(`/home/src/workspaces/project/src/main.ts`, `something();`) - }, - }, - }, - }, - { - subScenario: "react-jsx-emit-mode with no backing types found doesnt crash", - files: FileMap{ - "/home/src/workspaces/project/node_modules/react/jsx-runtime.js": "export {}", // js needs to be present so there's a resolution result - "/home/src/workspaces/project/node_modules/@types/react/index.d.ts": stringtestutil.Dedent(` - export {}; - declare global { - namespace JSX { - interface Element {} - interface IntrinsicElements { - div: { - propA?: boolean; - }; - } - } - }`), // doesn't contain a jsx-runtime definition - "/home/src/workspaces/project/src/index.tsx": `export const App = () =>
;`, - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "module": "commonjs", - "jsx": "react-jsx", - "incremental": true, - "jsxImportSource": "react" - } - }`), - }, - }, - { - subScenario: "react-jsx-emit-mode with no backing types found doesnt crash under --strict", - files: FileMap{ - "/home/src/workspaces/project/node_modules/react/jsx-runtime.js": "export {}", // js needs to be present so there's a resolution result - "/home/src/workspaces/project/node_modules/@types/react/index.d.ts": stringtestutil.Dedent(` - export {}; - declare global { - namespace JSX { - interface Element {} - interface IntrinsicElements { - div: { - propA?: boolean; - }; - } - } - }`), // doesn't contain a jsx-runtime definition - "/home/src/workspaces/project/src/index.tsx": `export const App = () =>
;`, - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "module": "commonjs", - "jsx": "react-jsx", - "incremental": true, - "jsxImportSource": "react" - } - }`), - }, - commandLineArgs: []string{"--strict"}, - }, - { - subScenario: "change to type that gets used as global through export in another file", - files: FileMap{ - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true - } - }`), - "/home/src/workspaces/project/class1.ts": stringtestutil.Dedent(` - const a: MagicNumber = 1; - console.log(a);`), - "/home/src/workspaces/project/constants.ts": "export default 1;", - "/home/src/workspaces/project/types.d.ts": `type MagicNumber = typeof import('./constants').default`, - }, - edits: []*tscEdit{ - { - caption: "Modify imports used in global file", - edit: func(sys *testSys) { - sys.writeFileNoError("/home/src/workspaces/project/constants.ts", "export default 2;", false) - }, - expectedDiff: "Currently there is issue with d.ts emit for export default = 1 to widen in dts which is why we are not re-computing errors and results in incorrect error reporting", - }, - }, - }, - { - subScenario: "change to type that gets used as global through export in another file through indirect import", - files: FileMap{ - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true - } - }`), - "/home/src/workspaces/project/class1.ts": stringtestutil.Dedent(` - const a: MagicNumber = 1; - console.log(a);`), - "/home/src/workspaces/project/constants.ts": "export default 1;", - "/home/src/workspaces/project/reexport.ts": `export { default as ConstantNumber } from "./constants"`, - "/home/src/workspaces/project/types.d.ts": `type MagicNumber = typeof import('./reexport').ConstantNumber`, - }, - edits: []*tscEdit{ - { - caption: "Modify imports used in global file", - edit: func(sys *testSys) { - sys.writeFileNoError("/home/src/workspaces/project/constants.ts", "export default 2;", false) - }, - expectedDiff: "Currently there is issue with d.ts emit for export default = 1 to widen in dts which is why we are not re-computing errors and results in incorrect error reporting", - }, - }, - }, - { - subScenario: "when file is deleted", - files: FileMap{ - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "outDir": "outDir" - } - }`), - "/home/src/workspaces/project/file1.ts": `export class C { }`, - "/home/src/workspaces/project/file2.ts": `export class D { }`, - }, - edits: []*tscEdit{ - { - caption: "delete file with imports", - edit: func(sys *testSys) { - sys.removeNoError("/home/src/workspaces/project/file2.ts") - }, - }, - }, - }, - { - subScenario: "generates typerefs correctly", - files: FileMap{ - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "outDir": "outDir", - "checkJs": true - }, - "include": ["src"], - }`), - "/home/src/workspaces/project/src/box.ts": stringtestutil.Dedent(` - export interface Box { - unbox(): T - } - `), - "/home/src/workspaces/project/src/bug.js": stringtestutil.Dedent(` - import * as B from "./box.js" - import * as W from "./wrap.js" - - /** - * @template {object} C - * @param {C} source - * @returns {W.Wrap} - */ - const wrap = source => { - throw source - } - - /** - * @returns {B.Box} - */ - const box = (n = 0) => ({ unbox: () => n }) - - export const bug = wrap({ n: box(1) }); - `), - "/home/src/workspaces/project/src/wrap.ts": stringtestutil.Dedent(` - export type Wrap = { - [K in keyof C]: { wrapped: C[K] } - } - `), - }, - edits: []*tscEdit{ - { - caption: "modify js file", - edit: func(sys *testSys) { - sys.appendFile("/home/src/workspaces/project/src/bug.js", `export const something = 1;`) - }, - }, - }, - }, - getConstEnumTest(` - export const enum A { - ONE = 1 - } - `, "/home/src/workspaces/project/b.d.ts", ""), - getConstEnumTest(` - export const enum AWorker { - ONE = 1 - } - export { AWorker as A }; - `, "/home/src/workspaces/project/b.d.ts", " aliased"), - getConstEnumTest(`export { AWorker as A } from "./worker";`, "/home/src/workspaces/project/worker.d.ts", " aliased in different file"), - { - subScenario: "option changes with composite", - files: FileMap{ - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - } - }`), - "/home/src/workspaces/project/a.ts": `export const a = 10;const aLocal = 10;`, - "/home/src/workspaces/project/b.ts": `export const b = 10;const bLocal = 10;`, - "/home/src/workspaces/project/c.ts": `import { a } from "./a";export const c = a;`, - "/home/src/workspaces/project/d.ts": `import { b } from "./b";export const d = b;`, - }, - edits: []*tscEdit{ - { - caption: "with sourceMap", - commandLineArgs: []string{"--sourceMap"}, - }, - { - caption: "should re-emit only js so they dont contain sourcemap", - }, - { - caption: "with declaration should not emit anything", - commandLineArgs: []string{"--declaration"}, - // discrepancyExplanation: () => [ - // `Clean build tsbuildinfo will have compilerOptions with composite and ${option.replace(/-/g, "")}`, - // `Incremental build will detect that it doesnt need to rebuild so tsbuild info is from before which has option composite only`, - // ], - }, - noChange, - { - caption: "with declaration and declarationMap", - commandLineArgs: []string{"--declaration", "--declarationMap"}, - }, - { - caption: "should re-emit only dts so they dont contain sourcemap", - }, - { - caption: "with emitDeclarationOnly should not emit anything", - commandLineArgs: []string{"--emitDeclarationOnly"}, - // discrepancyExplanation: () => [ - // `Clean build tsbuildinfo will have compilerOptions with composite and ${option.replace(/-/g, "")}`, - // `Incremental build will detect that it doesnt need to rebuild so tsbuild info is from before which has option composite only`, - // ], - }, - noChange, - { - caption: "local change", - edit: func(sys *testSys) { - sys.replaceFileText("/home/src/workspaces/project/a.ts", "Local = 1", "Local = 10") - }, - }, - { - caption: "with declaration should not emit anything", - commandLineArgs: []string{"--declaration"}, - // discrepancyExplanation: () => [ - // `Clean build tsbuildinfo will have compilerOptions with composite and ${option.replace(/-/g, "")}`, - // `Incremental build will detect that it doesnt need to rebuild so tsbuild info is from before which has option composite only`, - // ], - }, - { - caption: "with inlineSourceMap", - commandLineArgs: []string{"--inlineSourceMap"}, - }, - { - caption: "with sourceMap", - commandLineArgs: []string{"--sourceMap"}, - }, - { - caption: "declarationMap enabling", - edit: func(sys *testSys) { - sys.replaceFileText("/home/src/workspaces/project/tsconfig.json", `"composite": true,`, `"composite": true, "declarationMap": true`) - }, - }, - { - caption: "with sourceMap should not emit d.ts", - commandLineArgs: []string{"--sourceMap"}, - }, - }, - }, - { - subScenario: "option changes with incremental", - files: FileMap{ - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "incremental": true, - } - }`), - "/home/src/workspaces/project/a.ts": `export const a = 10;const aLocal = 10;`, - "/home/src/workspaces/project/b.ts": `export const b = 10;const bLocal = 10;`, - "/home/src/workspaces/project/c.ts": `import { a } from "./a";export const c = a;`, - "/home/src/workspaces/project/d.ts": `import { b } from "./b";export const d = b;`, - }, - edits: []*tscEdit{ - { - caption: "with sourceMap", - commandLineArgs: []string{"--sourceMap"}, - }, - { - caption: "should re-emit only js so they dont contain sourcemap", - }, - { - caption: "with declaration, emit Dts and should not emit js", - commandLineArgs: []string{"--declaration"}, - }, - { - caption: "with declaration and declarationMap", - commandLineArgs: []string{"--declaration", "--declarationMap"}, - }, - { - caption: "no change", - // discrepancyExplanation: () => [ - // `Clean build tsbuildinfo will have compilerOptions {}`, - // `Incremental build will detect that it doesnt need to rebuild so tsbuild info is from before which has option declaration and declarationMap`, - // ], - }, - { - caption: "local change", - edit: func(sys *testSys) { - sys.replaceFileText("/home/src/workspaces/project/a.ts", "Local = 1", "Local = 10") - }, - }, - { - caption: "with declaration and declarationMap", - commandLineArgs: []string{"--declaration", "--declarationMap"}, - }, - { - caption: "no change", - // discrepancyExplanation: () => [ - // `Clean build tsbuildinfo will have compilerOptions {}`, - // `Incremental build will detect that it doesnt need to rebuild so tsbuild info is from before which has option declaration and declarationMap`, - // ], - }, - { - caption: "with inlineSourceMap", - commandLineArgs: []string{"--inlineSourceMap"}, - }, - { - caption: "with sourceMap", - commandLineArgs: []string{"--sourceMap"}, - }, - { - caption: "emit js files", - }, - { - caption: "with declaration and declarationMap", - commandLineArgs: []string{"--declaration", "--declarationMap"}, - }, - { - caption: "with declaration and declarationMap, should not re-emit", - commandLineArgs: []string{"--declaration", "--declarationMap"}, - }, - }, - }, - { - subScenario: "when there is bind diagnostics thats ignored", - files: FileMap{ - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "skipLibCheck": true, - "incremental": true, - } - }`), - "/home/src/workspaces/project/a.ts": `export const a = 10;`, - "/home/src/workspaces/project/b.d.ts": stringtestutil.Dedent(` - interface NoName { - Profiler: new ({ sampleInterval: number, maxBufferSize: number }) => { - stop: () => Promise; - }; - } - `), - }, - commandLineArgs: []string{""}, - edits: []*tscEdit{ - noChange, - { - caption: "no change and tsc -b", - commandLineArgs: []string{"-b", "-v"}, - }, - }, - }, - } - - for _, test := range testCases { - test.run(t, "incremental") - } -} - -func TestTscLibraryResolution(t *testing.T) { - t.Parallel() - getTscLibraryResolutionFileMap := func(libReplacement bool) FileMap { - files := FileMap{ - "/home/src/workspace/projects/project1/utils.d.ts": `export const y = 10;`, - "/home/src/workspace/projects/project1/file.ts": `export const file = 10;`, - "/home/src/workspace/projects/project1/core.d.ts": `export const core = 10;`, - "/home/src/workspace/projects/project1/index.ts": `export const x = "type1";`, - "/home/src/workspace/projects/project1/file2.ts": stringtestutil.Dedent(` - /// - /// - /// - `), - "/home/src/workspace/projects/project1/tsconfig.json": stringtestutil.Dedent(fmt.Sprintf(` - { - "compilerOptions": { - "composite": true, - "typeRoots": ["./typeroot1"], - "lib": ["es5", "dom"], - "traceResolution": true, - "libReplacement": %t - } - } - `, libReplacement)), - "/home/src/workspace/projects/project1/typeroot1/sometype/index.d.ts": `export type TheNum = "type1";`, - "/home/src/workspace/projects/project2/utils.d.ts": `export const y = 10;`, - "/home/src/workspace/projects/project2/index.ts": `export const y = 10`, - "/home/src/workspace/projects/project2/tsconfig.json": stringtestutil.Dedent(fmt.Sprintf(` - { - "compilerOptions": { - "composite": true, - "lib": ["es5", "dom"], - "traceResolution": true, - "libReplacement": %t - } - } - `, libReplacement)), - "/home/src/workspace/projects/project3/utils.d.ts": `export const y = 10;`, - "/home/src/workspace/projects/project3/index.ts": `export const z = 10`, - "/home/src/workspace/projects/project3/tsconfig.json": stringtestutil.Dedent(fmt.Sprintf(` - { - "compilerOptions": { - "composite": true, - "lib": ["es5", "dom"], - "traceResolution": true, - "libReplacement": %t - } - } - `, libReplacement)), - "/home/src/workspace/projects/project4/utils.d.ts": `export const y = 10;`, - "/home/src/workspace/projects/project4/index.ts": `export const z = 10`, - "/home/src/workspace/projects/project4/tsconfig.json": stringtestutil.Dedent(fmt.Sprintf(` - { - "compilerOptions": { - "composite": true, - "lib": ["esnext", "dom", "webworker"], - "traceResolution": true, - "libReplacement": %t - } - } - `, libReplacement)), - getTestLibPathFor("dom"): "interface DOMInterface { }", - getTestLibPathFor("webworker"): "interface WebWorkerInterface { }", - getTestLibPathFor("scripthost"): "interface ScriptHostInterface { }", - "/home/src/workspace/projects/node_modules/@typescript/unlreated/index.d.ts": "export const unrelated = 10;", - } - if libReplacement { - files["/home/src/workspace/projects/node_modules/@typescript/lib-es5/index.d.ts"] = tscDefaultLibContent - files["/home/src/workspace/projects/node_modules/@typescript/lib-esnext/index.d.ts"] = tscDefaultLibContent - files["/home/src/workspace/projects/node_modules/@typescript/lib-dom/index.d.ts"] = "interface DOMInterface { }" - files["/home/src/workspace/projects/node_modules/@typescript/lib-webworker/index.d.ts"] = "interface WebWorkerInterface { }" - files["/home/src/workspace/projects/node_modules/@typescript/lib-scripthost/index.d.ts"] = "interface ScriptHostInterface { }" - } - return files - } - getTscLibResolutionTestCases := func(commandLineArgs []string) []*tscInput { - return []*tscInput{ - { - subScenario: "with config", - files: getTscLibraryResolutionFileMap(false), - cwd: "/home/src/workspace/projects", - commandLineArgs: commandLineArgs, - }, - { - subScenario: "with config with libReplacement", - files: getTscLibraryResolutionFileMap(true), - cwd: "/home/src/workspace/projects", - commandLineArgs: commandLineArgs, - }, - } - } - getTscLibraryResolutionUnknown := func() FileMap { - return FileMap{ - "/home/src/workspace/projects/project1/utils.d.ts": `export const y = 10;`, - "/home/src/workspace/projects/project1/file.ts": `export const file = 10;`, - "/home/src/workspace/projects/project1/core.d.ts": `export const core = 10;`, - "/home/src/workspace/projects/project1/index.ts": `export const x = "type1";`, - "/home/src/workspace/projects/project1/file2.ts": stringtestutil.Dedent(` - /// - /// - /// - `), - "/home/src/workspace/projects/project1/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "traceResolution": true, - "libReplacement": true - } - }`), - getTestLibPathFor("webworker"): "interface WebWorkerInterface { }", - getTestLibPathFor("scripthost"): "interface ScriptHostInterface { }", - } - } - testCases := slices.Concat( - getTscLibResolutionTestCases([]string{"-b", "project1", "project2", "project3", "project4", "--verbose", "--explainFiles"}), - getTscLibResolutionTestCases([]string{"-p", "project1", "--explainFiles"}), - getTscLibResolutionTestCases([]string{"-b", "-w", "project1", "project2", "project3", "project4", "--verbose", "--explainFiles"}), - []*tscInput{ - { - subScenario: "unknown lib", - files: getTscLibraryResolutionUnknown(), - cwd: "/home/src/workspace/projects", - commandLineArgs: []string{"-p", "project1", "--explainFiles"}, - }, - { - subScenario: "when noLib toggles", - files: FileMap{ - "/home/src/workspaces/project/a.d.ts": `declare const a = "hello";`, - "/home/src/workspaces/project/b.ts": `const b = 10;`, - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "declaration": true, - "incremental": true, - "lib": ["es6"], - }, - } - `), - }, - edits: []*tscEdit{ - { - caption: "with --noLib", - commandLineArgs: []string{"--noLib"}, - }, - }, - }, - }, - ) - - for _, test := range testCases { - test.run(t, "libraryResolution") - } -} - -func TestTscListFilesOnly(t *testing.T) { - t.Parallel() - testCases := []*tscInput{ - { - subScenario: "loose file", - files: FileMap{ - "/home/src/workspaces/project/test.ts": "export const x = 1;", - }, - commandLineArgs: []string{"test.ts", "--listFilesOnly"}, - }, - { - subScenario: "combined with incremental", - files: FileMap{ - "/home/src/workspaces/project/test.ts": "export const x = 1;", - "/home/src/workspaces/project/tsconfig.json": "{}", - }, - commandLineArgs: []string{"--incremental", "--listFilesOnly"}, - edits: []*tscEdit{ - { - caption: "incremental actual build", - commandLineArgs: []string{"--incremental"}, - }, - noChange, - { - caption: "incremental should not build", - commandLineArgs: []string{"--incremental"}, - }, - }, - }, - } - - for _, testCase := range testCases { - testCase.run(t, "listFilesOnly") - } -} - -func TestTscModuleResolution(t *testing.T) { - t.Parallel() - getBuildModuleResolutionInProjectRefTestCase := func(preserveSymlinks bool) *tscInput { - return &tscInput{ - subScenario: `resolves specifier in output declaration file from referenced project correctly` + core.IfElse(preserveSymlinks, " with preserveSymlinks", ""), - files: FileMap{ - `/user/username/projects/myproject/packages/pkg1/index.ts`: stringtestutil.Dedent(` - import type { TheNum } from 'pkg2' - export const theNum: TheNum = 42;`), - `/user/username/projects/myproject/packages/pkg1/tsconfig.json`: stringtestutil.Dedent(fmt.Sprintf(` - { - "compilerOptions": { - "outDir": "build", - "preserveSymlinks": %t - }, - "references": [{ "path": "../pkg2" }] - } - `, preserveSymlinks)), - `/user/username/projects/myproject/packages/pkg2/const.ts`: stringtestutil.Dedent(` - export type TheNum = 42; - `), - `/user/username/projects/myproject/packages/pkg2/index.ts`: stringtestutil.Dedent(` - export type { TheNum } from 'const'; - `), - `/user/username/projects/myproject/packages/pkg2/tsconfig.json`: stringtestutil.Dedent(fmt.Sprintf(` - { - "compilerOptions": { - "composite": true, - "outDir": "build", - "paths": { - "const": ["./const"] - }, - "preserveSymlinks": %t, - }, - } - `, preserveSymlinks)), - `/user/username/projects/myproject/packages/pkg2/package.json`: stringtestutil.Dedent(` - { - "name": "pkg2", - "version": "1.0.0", - "main": "build/index.js" - } - `), - `/user/username/projects/myproject/node_modules/pkg2`: vfstest.Symlink(`/user/username/projects/myproject/packages/pkg2`), - }, - cwd: "/user/username/projects/myproject", - commandLineArgs: []string{"-b", "packages/pkg1", "--verbose", "--traceResolution"}, - } - } - getTscModuleResolutionSharingFileMap := func() FileMap { - return FileMap{ - "/home/src/workspaces/project/packages/a/index.js": `export const a = 'a';`, - "/home/src/workspaces/project/packages/a/test/index.js": `import 'a';`, - "/home/src/workspaces/project/packages/a/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "checkJs": true, - "composite": true, - "declaration": true, - "emitDeclarationOnly": true, - "module": "nodenext", - "outDir": "types", - }, - }`), - "/home/src/workspaces/project/packages/a/package.json": stringtestutil.Dedent(` - { - "name": "a", - "version": "0.0.0", - "type": "module", - "exports": { - ".": { - "types": "./types/index.d.ts", - "default": "./index.js" - } - } - }`), - "/home/src/workspaces/project/packages/b/index.js": `export { a } from 'a';`, - "/home/src/workspaces/project/packages/b/tsconfig.json": stringtestutil.Dedent(` - { - "references": [{ "path": "../a" }], - "compilerOptions": { - "checkJs": true, - "module": "nodenext", - "noEmit": true, - "noImplicitAny": true, - }, - }`), - "/home/src/workspaces/project/packages/b/package.json": stringtestutil.Dedent(` - { - "name": "b", - "version": "0.0.0", - "type": "module" - }`), - "/home/src/workspaces/project/node_modules/a": vfstest.Symlink("/home/src/workspaces/project/packages/a"), - } - } - getTscModuleResolutionAlternateResultAtTypesPackageJson := func(packageName string, addTypesCondition bool) string { - var typesString string - if addTypesCondition { - typesString = `"types": "./index.d.ts",` - } - return stringtestutil.Dedent(fmt.Sprintf(` - { - "name": "@types/%s", - "version": "1.0.0", - "types": "index.d.ts", - "exports": { - ".": { - %s - "require": "./index.d.ts" - } - } - }`, packageName, typesString)) - } - getTscModuleResolutionAlternateResultPackageJson := func(packageName string, addTypes bool, addTypesCondition bool) string { - var types string - if addTypes { - types = `"types": "index.d.ts",` - } - var typesString string - if addTypesCondition { - typesString = `"types": "./index.d.ts",` - } - return stringtestutil.Dedent(fmt.Sprintf(` - { - "name": "%s", - "version": "1.0.0", - "main": "index.js", - %s - "exports": { - ".": { - %s - "import": "./index.mjs", - "require": "./index.js" - } - } - }`, packageName, types, typesString)) - } - getTscModuleResolutionAlternateResultDts := func(packageName string) string { - return fmt.Sprintf(`export declare const %s: number;`, packageName) - } - getTscModuleResolutionAlternateResultJs := func(packageName string) string { - return fmt.Sprintf(`module.exports = { %s: 1 };`, packageName) - } - getTscModuleResolutionAlternateResultMjs := func(packageName string) string { - return fmt.Sprintf(`export const %s = 1;`, packageName) - } - testCases := []*tscInput{ - getBuildModuleResolutionInProjectRefTestCase(false), - getBuildModuleResolutionInProjectRefTestCase(true), - { - subScenario: `type reference resolution uses correct options for different resolution options referenced project`, - files: FileMap{ - "/home/src/workspaces/project/packages/pkg1_index.ts": `export const theNum: TheNum = "type1";`, - "/home/src/workspaces/project/packages/pkg1.tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "typeRoots": ["./typeroot1"] - }, - "files": ["./pkg1_index.ts"], - } - `), - "/home/src/workspaces/project/packages/typeroot1/sometype/index.d.ts": `declare type TheNum = "type1";`, - "/home/src/workspaces/project/packages/pkg2_index.ts": `export const theNum: TheNum2 = "type2";`, - "/home/src/workspaces/project/packages/pkg2.tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "typeRoots": ["./typeroot2"] - }, - "files": ["./pkg2_index.ts"], - } - `), - "/home/src/workspaces/project/packages/typeroot2/sometype/index.d.ts": `declare type TheNum2 = "type2";`, - }, - commandLineArgs: []string{"-b", "packages/pkg1.tsconfig.json", "packages/pkg2.tsconfig.json", "--verbose", "--traceResolution"}, - }, - { - subScenario: "impliedNodeFormat differs between projects for shared file", - files: FileMap{ - "/home/src/workspaces/project/a/src/index.ts": "", - "/home/src/workspaces/project/a/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "strict": true - } - } - `), - "/home/src/workspaces/project/b/src/index.ts": stringtestutil.Dedent(` - import pg from "pg"; - pg.foo(); - `), - "/home/src/workspaces/project/b/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "strict": true, - "module": "node16" - }, - }`), - "/home/src/workspaces/project/b/package.json": stringtestutil.Dedent(` - { - "name": "b", - "type": "module" - }`), - "/home/src/workspaces/project/node_modules/@types/pg/index.d.ts": "export function foo(): void;", - "/home/src/workspaces/project/node_modules/@types/pg/package.json": stringtestutil.Dedent(` - { - "name": "@types/pg", - "types": "index.d.ts" - }`), - }, - commandLineArgs: []string{"-b", "a", "b", "--verbose", "--traceResolution", "--explainFiles"}, - edits: noChangeOnlyEdit, - }, - { - subScenario: "shared resolution should not report error", - files: getTscModuleResolutionSharingFileMap(), - commandLineArgs: []string{"-b", "packages/b", "--verbose", "--traceResolution", "--explainFiles"}, - }, - { - subScenario: "when resolution is not shared", - files: getTscModuleResolutionSharingFileMap(), - commandLineArgs: []string{"-b", "packages/a", "--verbose", "--traceResolution", "--explainFiles"}, - edits: []*tscEdit{ - { - caption: "build b", - commandLineArgs: []string{"-b", "packages/b", "--verbose", "--traceResolution", "--explainFiles"}, - }, - }, - }, - { - subScenario: "pnpm style layout", - files: FileMap{ - // button@0.0.1 - "/home/src/projects/component-type-checker/node_modules/.pnpm/@component-type-checker+button@0.0.1/node_modules/@component-type-checker/button/src/index.ts": stringtestutil.Dedent(` - export interface Button { - a: number; - b: number; - } - export function createButton(): Button { - return { - a: 0, - b: 1, - }; - } - `), - "/home/src/projects/component-type-checker/node_modules/.pnpm/@component-type-checker+button@0.0.1/node_modules/@component-type-checker/button/package.json": stringtestutil.Dedent(` - { - "name": "@component-type-checker/button", - "version": "0.0.1", - "main": "./src/index.ts" - }`), - - // button@0.0.2 - "/home/src/projects/component-type-checker/node_modules/.pnpm/@component-type-checker+button@0.0.2/node_modules/@component-type-checker/button/src/index.ts": stringtestutil.Dedent(` - export interface Button { - a: number; - c: number; - } - export function createButton(): Button { - return { - a: 0, - c: 2, - }; - } - `), - "/home/src/projects/component-type-checker/node_modules/.pnpm/@component-type-checker+button@0.0.2/node_modules/@component-type-checker/button/package.json": stringtestutil.Dedent(` - { - "name": "@component-type-checker/button", - "version": "0.0.2", - "main": "./src/index.ts" - }`), - - // @component-type-checker+components@0.0.1_@component-type-checker+button@0.0.1 - "/home/src/projects/component-type-checker/node_modules/.pnpm/@component-type-checker+components@0.0.1_@component-type-checker+button@0.0.1/node_modules/@component-type-checker/button": vfstest.Symlink( - "/home/src/projects/component-type-checker/node_modules/.pnpm/@component-type-checker+button@0.0.1/node_modules/@component-type-checker/button", - ), - "/home/src/projects/component-type-checker/node_modules/.pnpm/@component-type-checker+components@0.0.1_@component-type-checker+button@0.0.1/node_modules/@component-type-checker/components/src/index.ts": stringtestutil.Dedent(` - export { createButton, Button } from "@component-type-checker/button"; - `), - "/home/src/projects/component-type-checker/node_modules/.pnpm/@component-type-checker+components@0.0.1_@component-type-checker+button@0.0.1/node_modules/@component-type-checker/components/package.json": stringtestutil.Dedent(` - { - "name": "@component-type-checker/components", - "version": "0.0.1", - "main": "./src/index.ts", - "peerDependencies": { - "@component-type-checker/button": "*" - }, - "devDependencies": { - "@component-type-checker/button": "0.0.2" - } - }`), - - // @component-type-checker+components@0.0.1_@component-type-checker+button@0.0.2 - "/home/src/projects/component-type-checker/node_modules/.pnpm/@component-type-checker+components@0.0.1_@component-type-checker+button@0.0.2/node_modules/@component-type-checker/button": vfstest.Symlink( - "/home/src/projects/component-type-checker/node_modules/.pnpm/@component-type-checker+button@0.0.2/node_modules/@component-type-checker/button", - ), - "/home/src/projects/component-type-checker/node_modules/.pnpm/@component-type-checker+components@0.0.1_@component-type-checker+button@0.0.2/node_modules/@component-type-checker/components/src/index.ts": stringtestutil.Dedent(` - export { createButton, Button } from "@component-type-checker/button"; - `), - "/home/src/projects/component-type-checker/node_modules/.pnpm/@component-type-checker+components@0.0.1_@component-type-checker+button@0.0.2/node_modules/@component-type-checker/components/package.json": stringtestutil.Dedent(` - { - "name": "@component-type-checker/components", - "version": "0.0.1", - "main": "./src/index.ts", - "peerDependencies": { - "@component-type-checker/button": "*" - }, - "devDependencies": { - "@component-type-checker/button": "0.0.2" - } - }`), - - // sdk => @component-type-checker+components@0.0.1_@component-type-checker+button@0.0.1 - "/home/src/projects/component-type-checker/packages/sdk/src/index.ts": stringtestutil.Dedent(` - export { Button, createButton } from "@component-type-checker/components"; - export const VERSION = "0.0.2"; - `), - "/home/src/projects/component-type-checker/packages/sdk/package.json": stringtestutil.Dedent(` - { - "name": "@component-type-checker/sdk1", - "version": "0.0.2", - "main": "./src/index.ts", - "dependencies": { - "@component-type-checker/components": "0.0.1", - "@component-type-checker/button": "0.0.1" - } - }`), - "/home/src/projects/component-type-checker/packages/sdk/node_modules/@component-type-checker/button": vfstest.Symlink( - "/home/src/projects/component-type-checker/node_modules/.pnpm/@component-type-checker+button@0.0.1/node_modules/@component-type-checker/button", - ), - "/home/src/projects/component-type-checker/packages/sdk/node_modules/@component-type-checker/components": vfstest.Symlink( - "/home/src/projects/component-type-checker/node_modules/.pnpm/@component-type-checker+components@0.0.1_@component-type-checker+button@0.0.1/node_modules/@component-type-checker/components", - ), - - // app => @component-type-checker+components@0.0.1_@component-type-checker+button@0.0.2 - "/home/src/projects/component-type-checker/packages/app/src/app.tsx": stringtestutil.Dedent(` - import { VERSION } from "@component-type-checker/sdk"; - import { Button } from "@component-type-checker/components"; - import { createButton } from "@component-type-checker/button"; - const button: Button = createButton(); - `), - "/home/src/projects/component-type-checker/packages/app/package.json": stringtestutil.Dedent(` - { - "name": "app", - "version": "1.0.0", - "dependencies": { - "@component-type-checker/button": "0.0.2", - "@component-type-checker/components": "0.0.1", - "@component-type-checker/sdk": "0.0.2" - } - }`), - "/home/src/projects/component-type-checker/packages/app/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "target": "es5", - "module": "esnext", - "lib": ["ES5"], - "moduleResolution": "node", - "outDir": "dist", - }, - "include": ["src"], - }`), - "/home/src/projects/component-type-checker/packages/app/node_modules/@component-type-checker/button": vfstest.Symlink( - "/home/src/projects/component-type-checker/node_modules/.pnpm/@component-type-checker+button@0.0.2/node_modules/@component-type-checker/button", - ), - "/home/src/projects/component-type-checker/packages/app/node_modules/@component-type-checker/components": vfstest.Symlink( - "/home/src/projects/component-type-checker/node_modules/.pnpm/@component-type-checker+components@0.0.1_@component-type-checker+button@0.0.2/node_modules/@component-type-checker/components", - ), - "/home/src/projects/component-type-checker/packages/app/node_modules/@component-type-checker/sdk": vfstest.Symlink( - "/home/src/projects/component-type-checker/packages/sdk", - ), - }, - cwd: "/home/src/projects/component-type-checker/packages/app", - commandLineArgs: []string{"--traceResolution", "--explainFiles"}, - }, - { - subScenario: "package json scope", - files: FileMap{ - "/home/src/workspaces/project/src/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "target": "ES2016", - "composite": true, - "module": "Node16", - "traceResolution": true, - }, - "files": [ - "main.ts", - "fileA.ts", - "fileB.mts", - ], - }`), - "/home/src/workspaces/project/src/main.ts": "export const x = 10;", - "/home/src/workspaces/project/src/fileA.ts": stringtestutil.Dedent(` - import { foo } from "./fileB.mjs"; - foo(); - `), - "/home/src/workspaces/project/src/fileB.mts": "export function foo() {}", - "/home/src/workspaces/project/package.json": stringtestutil.Dedent(` - { - "name": "app", - "version": "1.0.0" - } - `), - }, - commandLineArgs: []string{"-p", "src", "--explainFiles", "--extendedDiagnostics"}, - edits: []*tscEdit{ - { - caption: "Delete package.json", - edit: func(sys *testSys) { - sys.removeNoError("/home/src/workspaces/project/package.json") - }, - // !!! repopulateInfo on diagnostics not yet implemented - expectedDiff: "Currently we arent repopulating error chain so errors will be different", - }, - }, - }, - { - subScenario: "alternateResult", - files: FileMap{ - "/home/src/projects/project/node_modules/@types/bar/package.json": getTscModuleResolutionAlternateResultAtTypesPackageJson("bar" /*addTypesCondition*/, false), - "/home/src/projects/project/node_modules/@types/bar/index.d.ts": getTscModuleResolutionAlternateResultDts("bar"), - "/home/src/projects/project/node_modules/bar/package.json": getTscModuleResolutionAlternateResultPackageJson("bar" /*addTypes*/, false /*addTypesCondition*/, false), - "/home/src/projects/project/node_modules/bar/index.js": getTscModuleResolutionAlternateResultJs("bar"), - "/home/src/projects/project/node_modules/bar/index.mjs": getTscModuleResolutionAlternateResultMjs("bar"), - "/home/src/projects/project/node_modules/foo/package.json": getTscModuleResolutionAlternateResultPackageJson("foo" /*addTypes*/, true /*addTypesCondition*/, false), - "/home/src/projects/project/node_modules/foo/index.js": getTscModuleResolutionAlternateResultJs("foo"), - "/home/src/projects/project/node_modules/foo/index.mjs": getTscModuleResolutionAlternateResultMjs("foo"), - "/home/src/projects/project/node_modules/foo/index.d.ts": getTscModuleResolutionAlternateResultDts("foo"), - "/home/src/projects/project/node_modules/@types/bar2/package.json": getTscModuleResolutionAlternateResultAtTypesPackageJson("bar2" /*addTypesCondition*/, true), - "/home/src/projects/project/node_modules/@types/bar2/index.d.ts": getTscModuleResolutionAlternateResultDts("bar2"), - "/home/src/projects/project/node_modules/bar2/package.json": getTscModuleResolutionAlternateResultPackageJson("bar2" /*addTypes*/, false /*addTypesCondition*/, false), - "/home/src/projects/project/node_modules/bar2/index.js": getTscModuleResolutionAlternateResultJs("bar2"), - "/home/src/projects/project/node_modules/bar2/index.mjs": getTscModuleResolutionAlternateResultMjs("bar2"), - "/home/src/projects/project/node_modules/foo2/package.json": getTscModuleResolutionAlternateResultPackageJson("foo2" /*addTypes*/, true /*addTypesCondition*/, true), - "/home/src/projects/project/node_modules/foo2/index.js": getTscModuleResolutionAlternateResultJs("foo2"), - "/home/src/projects/project/node_modules/foo2/index.mjs": getTscModuleResolutionAlternateResultMjs("foo2"), - "/home/src/projects/project/node_modules/foo2/index.d.ts": getTscModuleResolutionAlternateResultDts("foo2"), - "/home/src/projects/project/index.mts": stringtestutil.Dedent(` - import { foo } from "foo"; - import { bar } from "bar"; - import { foo2 } from "foo2"; - import { bar2 } from "bar2"; - `), - "/home/src/projects/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "module": "node16", - "moduleResolution": "node16", - "traceResolution": true, - "incremental": true, - "strict": true, - "types": [], - }, - "files": ["index.mts"], - }`), - }, - cwd: "/home/src/projects/project", - edits: []*tscEdit{ - { - caption: "delete the alternateResult in @types", - edit: func(sys *testSys) { - sys.removeNoError("/home/src/projects/project/node_modules/@types/bar/index.d.ts") - }, - // !!! repopulateInfo on diagnostics not yet implemented - expectedDiff: "Currently we arent repopulating error chain so errors will be different", - }, - { - caption: "delete the node10Result in package/types", - edit: func(sys *testSys) { - sys.removeNoError("/home/src/projects/project/node_modules/foo/index.d.ts") - }, - // !!! repopulateInfo on diagnostics not yet implemented - expectedDiff: "Currently we arent repopulating error chain so errors will be different", - }, - { - caption: "add the alternateResult in @types", - edit: func(sys *testSys) { - sys.writeFileNoError("/home/src/projects/project/node_modules/@types/bar/index.d.ts", getTscModuleResolutionAlternateResultDts("bar"), false) - }, - // !!! repopulateInfo on diagnostics not yet implemented - expectedDiff: "Currently we arent repopulating error chain so errors will be different", - }, - { - caption: "add the alternateResult in package/types", - edit: func(sys *testSys) { - sys.writeFileNoError("/home/src/projects/project/node_modules/foo/index.d.ts", getTscModuleResolutionAlternateResultDts("foo"), false) - }, - }, - { - caption: "update package.json from @types so error is fixed", - edit: func(sys *testSys) { - sys.writeFileNoError("/home/src/projects/project/node_modules/@types/bar/package.json", getTscModuleResolutionAlternateResultAtTypesPackageJson("bar" /*addTypesCondition*/, true), false) - }, - }, - { - caption: "update package.json so error is fixed", - edit: func(sys *testSys) { - sys.writeFileNoError("/home/src/projects/project/node_modules/foo/package.json", getTscModuleResolutionAlternateResultPackageJson("foo" /*addTypes*/, true /*addTypesCondition*/, true), false) - }, - }, - { - caption: "update package.json from @types so error is introduced", - edit: func(sys *testSys) { - sys.writeFileNoError("/home/src/projects/project/node_modules/@types/bar2/package.json", getTscModuleResolutionAlternateResultAtTypesPackageJson("bar2" /*addTypesCondition*/, false), false) - }, - }, - { - caption: "update package.json so error is introduced", - edit: func(sys *testSys) { - sys.writeFileNoError("/home/src/projects/project/node_modules/foo2/package.json", getTscModuleResolutionAlternateResultPackageJson("foo2" /*addTypes*/, true /*addTypesCondition*/, false), false) - }, - }, - { - caption: "delete the alternateResult in @types", - edit: func(sys *testSys) { - sys.removeNoError("/home/src/projects/project/node_modules/@types/bar2/index.d.ts") - }, - // !!! repopulateInfo on diagnostics not yet implemented - expectedDiff: "Currently we arent repopulating error chain so errors will be different", - }, - { - caption: "delete the node10Result in package/types", - edit: func(sys *testSys) { - sys.removeNoError("/home/src/projects/project/node_modules/foo2/index.d.ts") - }, - // !!! repopulateInfo on diagnostics not yet implemented - expectedDiff: "Currently we arent repopulating error chain so errors will be different", - }, - { - caption: "add the alternateResult in @types", - edit: func(sys *testSys) { - sys.writeFileNoError("/home/src/projects/project/node_modules/@types/bar2/index.d.ts", getTscModuleResolutionAlternateResultDts("bar2"), false) - }, - // !!! repopulateInfo on diagnostics not yet implemented - expectedDiff: "Currently we arent repopulating error chain so errors will be different", - }, - { - caption: "add the ndoe10Result in package/types", - edit: func(sys *testSys) { - sys.writeFileNoError("/home/src/projects/project/node_modules/foo2/index.d.ts", getTscModuleResolutionAlternateResultDts("foo2"), false) - }, - }, - }, - }, - { - subScenario: "handles the cache correctly when two projects use different module resolution settings", - files: FileMap{ - `/user/username/projects/myproject/project1/index.ts`: `import { foo } from "file";`, - `/user/username/projects/myproject/project1/node_modules/file/index.d.ts`: "export const foo = 10;", - `/user/username/projects/myproject/project1/tsconfig.json`: stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "types": ["foo", "bar"] - }, - "files": ["index.ts"], - }`), - `/user/username/projects/myproject/project2/index.ts`: `import { foo } from "file";`, - `/user/username/projects/myproject/project2/node_modules/file/index.d.ts`: "export const foo = 10;", - `/user/username/projects/myproject/project2/tsconfig.json`: stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "types": ["foo"], - "module": "nodenext", - "moduleResolution": "nodenext" - }, - "files": ["index.ts"], - }`), - `/user/username/projects/myproject/node_modules/@types/foo/index.d.ts`: "export const foo = 10;", - `/user/username/projects/myproject/node_modules/@types/bar/index.d.ts`: "export const bar = 10;", - `/user/username/projects/myproject/tsconfig.json`: stringtestutil.Dedent(` - { - "files": [], - "references": [ - { "path": "./project1" }, - { "path": "./project2" }, - ], - }`), - }, - cwd: "/user/username/projects/myproject", - commandLineArgs: []string{"--b", "-w", "-v"}, - edits: []*tscEdit{ - { - caption: "Append text", - edit: func(sys *testSys) { - sys.appendFile(`/user/username/projects/myproject/project1/index.ts`, "const bar = 10;") - }, - }, - }, - }, - { - // !!! sheetal package.json watches not yet implemented - subScenario: `resolves specifier in output declaration file from referenced project correctly with cts and mts extensions`, - files: FileMap{ - `/user/username/projects/myproject/packages/pkg1/package.json`: stringtestutil.Dedent(` - { - "name": "pkg1", - "version": "1.0.0", - "main": "build/index.js", - "type": "module" - }`), - `/user/username/projects/myproject/packages/pkg1/index.ts`: stringtestutil.Dedent(` - import type { TheNum } from 'pkg2' - export const theNum: TheNum = 42;`), - `/user/username/projects/myproject/packages/pkg1/tsconfig.json`: stringtestutil.Dedent(` - { - "compilerOptions": { - "outDir": "build", - "module": "node16", - }, - "references": [{ "path": "../pkg2" }], - }`), - `/user/username/projects/myproject/packages/pkg2/const.cts`: `export type TheNum = 42;`, - `/user/username/projects/myproject/packages/pkg2/index.ts`: `export type { TheNum } from './const.cjs';`, - `/user/username/projects/myproject/packages/pkg2/tsconfig.json`: stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "outDir": "build", - "module": "node16", - }, - }`), - `/user/username/projects/myproject/packages/pkg2/package.json`: stringtestutil.Dedent(` - { - "name": "pkg2", - "version": "1.0.0", - "main": "build/index.js", - "type": "module" - }`), - `/user/username/projects/myproject/node_modules/pkg2`: vfstest.Symlink(`/user/username/projects/myproject/packages/pkg2`), - }, - cwd: "/user/username/projects/myproject", - commandLineArgs: []string{"-b", "packages/pkg1", "-w", "--verbose", "--traceResolution"}, - edits: []*tscEdit{ - { - caption: "reports import errors after change to package file", - edit: func(sys *testSys) { - sys.replaceFileText(`/user/username/projects/myproject/packages/pkg1/package.json`, `"module"`, `"commonjs"`) - }, - expectedDiff: "Package.json watch pending, so no change detected yet", - }, - { - caption: "removes those errors when a package file is changed back", - edit: func(sys *testSys) { - sys.replaceFileText(`/user/username/projects/myproject/packages/pkg1/package.json`, `"commonjs"`, `"module"`) - }, - }, - { - caption: "reports import errors after change to package file", - edit: func(sys *testSys) { - sys.replaceFileText(`/user/username/projects/myproject/packages/pkg1/package.json`, `"module"`, `"commonjs"`) - }, - expectedDiff: "Package.json watch pending, so no change detected yet", - }, - { - caption: "removes those errors when a package file is changed to cjs extensions", - edit: func(sys *testSys) { - sys.replaceFileText(`/user/username/projects/myproject/packages/pkg2/package.json`, `"build/index.js"`, `"build/index.cjs"`) - sys.renameFileNoError(`/user/username/projects/myproject/packages/pkg2/index.ts`, `/user/username/projects/myproject/packages/pkg2/index.cts`) - }, - }, - }, - }, - { - subScenario: `build mode watches for changes to package-json main fields`, - files: FileMap{ - `/user/username/projects/myproject/packages/pkg1/package.json`: stringtestutil.Dedent(` - { - "name": "pkg1", - "version": "1.0.0", - "main": "build/index.js" - }`), - `/user/username/projects/myproject/packages/pkg1/index.ts`: stringtestutil.Dedent(` - import type { TheNum } from 'pkg2' - export const theNum: TheNum = 42;`), - `/user/username/projects/myproject/packages/pkg1/tsconfig.json`: stringtestutil.Dedent(` - { - "compilerOptions": { - "outDir": "build", - }, - "references": [{ "path": "../pkg2" }], - }`), - `/user/username/projects/myproject/packages/pkg2/tsconfig.json`: stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "outDir": "build", - }, - }`), - `/user/username/projects/myproject/packages/pkg2/const.ts`: `export type TheNum = 42;`, - `/user/username/projects/myproject/packages/pkg2/index.ts`: `export type { TheNum } from './const.js';`, - `/user/username/projects/myproject/packages/pkg2/other.ts`: `export type TheStr = string;`, - `/user/username/projects/myproject/packages/pkg2/package.json`: stringtestutil.Dedent(` - { - "name": "pkg2", - "version": "1.0.0", - "main": "build/index.js" - }`), - `/user/username/projects/myproject/node_modules/pkg2`: vfstest.Symlink(`/user/username/projects/myproject/packages/pkg2`), - }, - cwd: "/user/username/projects/myproject", - commandLineArgs: []string{"-b", "packages/pkg1", "--verbose", "-w", "--traceResolution"}, - edits: []*tscEdit{ - { - caption: "reports import errors after change to package file", - edit: func(sys *testSys) { - sys.replaceFileText(`/user/username/projects/myproject/packages/pkg2/package.json`, `index.js`, `other.js`) - }, - expectedDiff: "Package.json watch pending, so no change detected yet", - }, - { - caption: "removes those errors when a package file is changed back", - edit: func(sys *testSys) { - sys.replaceFileText(`/user/username/projects/myproject/packages/pkg2/package.json`, `other.js`, `index.js`) - }, - }, - }, - }, - { - subScenario: "resolution from d.ts of referenced project", - files: FileMap{ - "/home/src/workspaces/project/common.d.ts": "export type OnValue = (value: number) => void", - "/home/src/workspaces/project/producer/index.ts": stringtestutil.Dedent(` - export { ValueProducerDeclaration } from "./in-js" - import { OnValue } from "@common" - export interface ValueProducerFromTs { - onValue: OnValue; - } - `), - "/home/src/workspaces/project/producer/in-js.d.ts": stringtestutil.Dedent(` - import { OnValue } from "@common" - export interface ValueProducerDeclaration { - onValue: OnValue; - } - `), - "/home/src/workspaces/project/producer/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "strict": true, - "composite": true, - "module": "nodenext", - "moduleResolution": "nodenext", - "paths": { - "@common": ["../common.d.ts"], - }, - }, - }`), - "/home/src/workspaces/project/consumer/index.ts": stringtestutil.Dedent(` - import { ValueProducerDeclaration, ValueProducerFromTs } from "@producer" - declare let v: ValueProducerDeclaration; - // n is implicitly any because onValue is actually any (despite what the tooltip says) - v.onValue = (n) => { - } - // n is implicitly number as expected - declare let v2: ValueProducerFromTs; - v2.onValue = (n) => { - }`), - "/home/src/workspaces/project/consumer/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "strict": true, - "module": "nodenext", - "moduleResolution": "nodenext", - "paths": { - "@producer": ["../producer/index"], - }, - }, - "references": [ - { "path": "../producer" }, - ], - }`), - }, - commandLineArgs: []string{"--b", "consumer", "--traceResolution", "-v"}, - }, - } - - for _, test := range testCases { - test.run(t, "moduleResolution") - } -} - -func TestTscNoCheck(t *testing.T) { - t.Parallel() - type noCheckScenario struct { - subScenario string - aText string - } - getTscNoCheckTestCase := func(scenario *noCheckScenario, incremental bool, commandLineArgs []string) *tscInput { - noChangeWithCheck := &tscEdit{ - caption: "No Change run with checking", - commandLineArgs: commandLineArgs, - } - fixErrorNoCheck := &tscEdit{ - caption: "Fix `a` error with noCheck", - edit: func(sys *testSys) { - sys.writeFileNoError("/home/src/workspaces/project/a.ts", `export const a = "hello";`, false) - }, - } - addErrorNoCheck := &tscEdit{ - caption: "Introduce error with noCheck", - edit: func(sys *testSys) { - sys.writeFileNoError("/home/src/workspaces/project/a.ts", scenario.aText, false) - }, - } - return &tscInput{ - subScenario: scenario.subScenario + core.IfElse(incremental, " with incremental", ""), - files: FileMap{ - "/home/src/workspaces/project/a.ts": scenario.aText, - "/home/src/workspaces/project/b.ts": `export const b = 10;`, - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(fmt.Sprintf(` - { - "compilerOptions": { - "declaration": true, - "incremental": %t - } - }`, incremental)), - }, - commandLineArgs: slices.Concat(commandLineArgs, []string{"--noCheck"}), - edits: []*tscEdit{ - noChange, - fixErrorNoCheck, // Fix error with noCheck - noChange, // Should be no op - noChangeWithCheck, // Check errors - should not report any errors - update buildInfo - noChangeWithCheck, // Should be no op - noChange, // Should be no op - addErrorNoCheck, - noChange, // Should be no op - noChangeWithCheck, // Should check errors and update buildInfo - fixErrorNoCheck, // Fix error with noCheck - noChangeWithCheck, // Should check errors and update buildInfo - { - caption: "Add file with error", - edit: func(sys *testSys) { - sys.writeFileNoError("/home/src/workspaces/project/c.ts", `export const c: number = "hello";`, false) - }, - commandLineArgs: commandLineArgs, - }, - addErrorNoCheck, - fixErrorNoCheck, - noChangeWithCheck, - noChange, // Should be no op - noChangeWithCheck, // Should be no op - }, - } - } - - cases := []noCheckScenario{ - {"syntax errors", `export const a = "hello`}, - {"semantic errors", `export const a: number = "hello";`}, - {"dts errors", `export const a = class { private p = 10; };`}, - } - testCases := core.FlatMap(cases, func(c noCheckScenario) []*tscInput { - return []*tscInput{ - getTscNoCheckTestCase(&c, false, []string{}), - getTscNoCheckTestCase(&c, true, []string{}), - getTscNoCheckTestCase(&c, false, []string{"-b", "-v"}), - getTscNoCheckTestCase(&c, true, []string{"-b", "-v"}), - } - }) - for _, test := range testCases { - test.run(t, "noCheck") - } -} - -func TestTscNoEmit(t *testing.T) { - t.Parallel() - type tscNoEmitScenario struct { - subScenario string - aText string - dtsEnabled bool - } - noEmitScenarios := []*tscNoEmitScenario{ - { - subScenario: "syntax errors", - aText: `const a = "hello`, - }, - { - subScenario: "semantic errors", - aText: `const a: number = "hello"`, - }, - { - subScenario: "dts errors", - aText: `const a = class { private p = 10; };`, - dtsEnabled: true, - }, - { - subScenario: "dts errors without dts enabled", - aText: `const a = class { private p = 10; };`, - }, - } - getTscNoEmitAndErrorsFileMap := func(scenario *tscNoEmitScenario, incremental bool, asModules bool, modify func(FileMap)) FileMap { - files := FileMap{ - "/home/src/projects/project/a.ts": core.IfElse(asModules, `export `, "") + scenario.aText, - "/home/src/projects/project/tsconfig.json": stringtestutil.Dedent(fmt.Sprintf(` - { - "compilerOptions": { - "incremental": %t, - "declaration": %t - } - } - `, incremental, scenario.dtsEnabled)), - } - if asModules { - files["/home/src/projects/project/b.ts"] = `export const b = 10;` - } - if modify != nil { - modify(files) - } - return files - } - getTscNoEmitAndErrorsTestCasesWorker := func(commandLineArgs []string, addNoEmitOnCommandLine bool, modify func(FileMap), edits func(scenario *tscNoEmitScenario, commandLineArgs []string, asModules bool) []*tscEdit) []*tscInput { - testingCases := make([]*tscInput, 0, len(noEmitScenarios)*3) - commandLineArgsForInput := commandLineArgs - if addNoEmitOnCommandLine { - commandLineArgsForInput = slices.Concat(commandLineArgs, []string{"--noEmit"}) - } - for _, scenario := range noEmitScenarios { - testingCases = append( - testingCases, - &tscInput{ - subScenario: scenario.subScenario, - commandLineArgs: commandLineArgsForInput, - files: getTscNoEmitAndErrorsFileMap(scenario, false, false, modify), - cwd: "/home/src/projects/project", - edits: edits(scenario, commandLineArgs, false), - }, - &tscInput{ - subScenario: scenario.subScenario + " with incremental", - commandLineArgs: commandLineArgsForInput, - files: getTscNoEmitAndErrorsFileMap(scenario, true, false, modify), - cwd: "/home/src/projects/project", - edits: edits(scenario, commandLineArgs, false), - }, - &tscInput{ - subScenario: scenario.subScenario + " with incremental as modules", - commandLineArgs: commandLineArgsForInput, - files: getTscNoEmitAndErrorsFileMap(scenario, true, true, modify), - cwd: "/home/src/projects/project", - edits: edits(scenario, commandLineArgs, true), - }, - ) - } - return testingCases - } - getTscNoEmitAndErrorsTestCases := func(commandLineArgs []string) []*tscInput { - return getTscNoEmitAndErrorsTestCasesWorker( - commandLineArgs, - true, - nil, - func(scenario *tscNoEmitScenario, commandLineArgs []string, asModules bool) []*tscEdit { - fixedATsContent := core.IfElse(asModules, "export ", "") + `const a = "hello";` - return []*tscEdit{ - noChange, - { - caption: "Fix error", - edit: func(sys *testSys) { - sys.writeFileNoError("/home/src/projects/project/a.ts", fixedATsContent, false) - }, - }, - noChange, - { - caption: "Emit after fixing error", - commandLineArgs: commandLineArgs, - }, - noChange, - { - caption: "Introduce error", - edit: func(sys *testSys) { - sys.writeFileNoError("/home/src/projects/project/a.ts", scenario.aText, false) - }, - }, - { - caption: "Emit when error", - commandLineArgs: commandLineArgs, - }, - noChange, - } - }, - ) - } - getTscNoEmitAndErrorsWatchTestCases := func(commandLineArgs []string) []*tscInput { - return getTscNoEmitAndErrorsTestCasesWorker( - commandLineArgs, - false, - func(files FileMap) { - files["/home/src/projects/project/tsconfig.json"] = strings.Replace(files["/home/src/projects/project/tsconfig.json"].(string), "}", `, "noEmit": true }`, 1) - }, - func(scenario *tscNoEmitScenario, commandLineArgs []string, asModules bool) []*tscEdit { - fixedATsContent := core.IfElse(asModules, "export ", "") + `const a = "hello";` - return []*tscEdit{ - { - caption: "Fix error", - edit: func(sys *testSys) { - sys.writeFileNoError("/home/src/projects/project/a.ts", fixedATsContent, false) - }, - }, - { - caption: "Emit after fixing error", - edit: func(sys *testSys) { - sys.replaceFileText("/home/src/projects/project/tsconfig.json", `"noEmit": true`, `"noEmit": false`) - }, - }, - { - caption: "no Emit run after fixing error", - edit: func(sys *testSys) { - sys.replaceFileText("/home/src/projects/project/tsconfig.json", `"noEmit": false`, `"noEmit": true`) - }, - }, - { - caption: "Introduce error", - edit: func(sys *testSys) { - sys.writeFileNoError("/home/src/projects/project/a.ts", scenario.aText, false) - }, - }, - { - caption: "Emit when error", - edit: func(sys *testSys) { - sys.replaceFileText("/home/src/projects/project/tsconfig.json", `"noEmit": true`, `"noEmit": false`) - }, - }, - { - caption: "no Emit run when error", - edit: func(sys *testSys) { - sys.replaceFileText("/home/src/projects/project/tsconfig.json", `"noEmit": false`, `"noEmit": true`) - }, - }, - } - }, - ) - } - getTscNoEmitChangesFileMap := func(optionsStr string) FileMap { - return FileMap{ - "/home/src/workspaces/project/src/class.ts": stringtestutil.Dedent(` - export class classC { - prop = 1; - }`), - "/home/src/workspaces/project/src/indirectClass.ts": stringtestutil.Dedent(` - import { classC } from './class'; - export class indirectClass { - classC = new classC(); - }`), - "/home/src/workspaces/project/src/directUse.ts": stringtestutil.Dedent(` - import { indirectClass } from './indirectClass'; - new indirectClass().classC.prop;`), - "/home/src/workspaces/project/src/indirectUse.ts": stringtestutil.Dedent(` - import { indirectClass } from './indirectClass'; - new indirectClass().classC.prop;`), - "/home/src/workspaces/project/src/noChangeFile.ts": stringtestutil.Dedent(` - export function writeLog(s: string) { - }`), - "/home/src/workspaces/project/src/noChangeFileWithEmitSpecificError.ts": stringtestutil.Dedent(` - function someFunc(arguments: boolean, ...rest: any[]) { - }`), - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(fmt.Sprintf(` - { - "compilerOptions": { %s } - }`, optionsStr)), - } - } - - type tscNoEmitChangesScenario struct { - subScenario string - optionsString string - } - noEmitChangesScenarios := []*tscNoEmitChangesScenario{ - { - // !!! sheetal missing initial reporting of Duplicate_identifier_arguments_Compiler_uses_arguments_to_initialize_rest_parameters is absent - subScenario: "composite", - optionsString: `"composite": true`, - }, - { - subScenario: "incremental declaration", - optionsString: `"incremental": true, "declaration": true`, - }, - { - subScenario: "incremental", - optionsString: `"incremental": true`, - }, - } - getTscNoEmitChangesTestCases := func(commandLineArgs []string) []*tscInput { - noChangeWithNoEmit := &tscEdit{ - caption: "No Change run with noEmit", - commandLineArgs: slices.Concat(commandLineArgs, []string{"--noEmit"}), - } - noChangeWithEmit := &tscEdit{ - caption: "No Change run with emit", - commandLineArgs: commandLineArgs, - } - introduceError := func(sys *testSys) { - sys.replaceFileText("/home/src/workspaces/project/src/class.ts", "prop", "prop1") - } - fixError := func(sys *testSys) { - sys.replaceFileText("/home/src/workspaces/project/src/class.ts", "prop1", "prop") - } - testCases := make([]*tscInput, 0, len(noEmitChangesScenarios)) - for _, scenario := range noEmitChangesScenarios { - testCases = append( - testCases, - &tscInput{ - subScenario: "changes " + scenario.subScenario, - commandLineArgs: commandLineArgs, - files: getTscNoEmitChangesFileMap(scenario.optionsString), - edits: []*tscEdit{ - noChangeWithNoEmit, - noChangeWithNoEmit, - { - caption: "Introduce error but still noEmit", - commandLineArgs: noChangeWithNoEmit.commandLineArgs, - edit: introduceError, - }, - { - caption: "Fix error and emit", - edit: fixError, - }, - noChangeWithEmit, - noChangeWithNoEmit, - noChangeWithNoEmit, - noChangeWithEmit, - { - caption: "Introduce error and emit", - edit: introduceError, - }, - noChangeWithEmit, - noChangeWithNoEmit, - noChangeWithNoEmit, - noChangeWithEmit, - { - caption: "Fix error and no emit", - commandLineArgs: noChangeWithNoEmit.commandLineArgs, - edit: fixError, - }, - noChangeWithEmit, - noChangeWithNoEmit, - noChangeWithNoEmit, - noChangeWithEmit, - }, - }, - &tscInput{ - subScenario: "changes with initial noEmit " + scenario.subScenario, - commandLineArgs: noChangeWithNoEmit.commandLineArgs, - files: getTscNoEmitChangesFileMap(scenario.optionsString), - edits: []*tscEdit{ - noChangeWithEmit, - { - caption: "Introduce error with emit", - commandLineArgs: commandLineArgs, - edit: introduceError, - }, - { - caption: "Fix error and no emit", - edit: fixError, - }, - noChangeWithEmit, - }, - }, - ) - } - return testCases - } - getTscNoEmitDtsChangesFileMap := func(incremental bool, asModules bool) FileMap { - files := FileMap{ - "/home/src/projects/project/a.ts": core.IfElse(asModules, `export const a = class { private p = 10; };`, `const a = class { private p = 10; };`), - "/home/src/projects/project/tsconfig.json": stringtestutil.Dedent(fmt.Sprintf(` - { - "compilerOptions": { - "incremental": %t, - } - } - `, incremental)), - } - if asModules { - files["/home/src/projects/project/b.ts"] = `export const b = 10;` - } - return files - } - getTscNoEmitDtsChangesEdits := func(commandLineArgs []string) []*tscEdit { - return []*tscEdit{ - noChange, - { - caption: "With declaration enabled noEmit - Should report errors", - commandLineArgs: slices.Concat(commandLineArgs, []string{"--noEmit", "--declaration"}), - }, - { - caption: "With declaration and declarationMap noEmit - Should report errors", - commandLineArgs: slices.Concat(commandLineArgs, []string{"--noEmit", "--declaration", "--declarationMap"}), - }, - noChange, - { - caption: "Dts Emit with error", - commandLineArgs: slices.Concat(commandLineArgs, []string{"--declaration"}), - }, - { - caption: "Fix the error", - edit: func(sys *testSys) { - sys.replaceFileText("/home/src/projects/project/a.ts", "private", "public") - }, - }, - { - caption: "With declaration enabled noEmit", - commandLineArgs: slices.Concat(commandLineArgs, []string{"--noEmit", "--declaration"}), - }, - { - caption: "With declaration and declarationMap noEmit", - commandLineArgs: slices.Concat(commandLineArgs, []string{"--noEmit", "--declaration", "--declarationMap"}), - }, - } - } - getTscNoEmitDtsChangesTestCases := func() []*tscInput { - return []*tscInput{ - { - subScenario: "dts errors with declaration enable changes", - commandLineArgs: []string{"-b", "-v", "--noEmit"}, - files: getTscNoEmitDtsChangesFileMap(false, false), - cwd: "/home/src/projects/project", - edits: getTscNoEmitDtsChangesEdits([]string{"-b", "-v"}), - }, - { - subScenario: "dts errors with declaration enable changes with incremental", - commandLineArgs: []string{"-b", "-v", "--noEmit"}, - files: getTscNoEmitDtsChangesFileMap(true, false), - cwd: "/home/src/projects/project", - edits: getTscNoEmitDtsChangesEdits([]string{"-b", "-v"}), - }, - { - subScenario: "dts errors with declaration enable changes with incremental as modules", - commandLineArgs: []string{"-b", "-v", "--noEmit"}, - files: getTscNoEmitDtsChangesFileMap(true, true), - cwd: "/home/src/projects/project", - edits: getTscNoEmitDtsChangesEdits([]string{"-b", "-v"}), - }, - } - } - getTscNoEmitDtsChangesMultiFileErrorsTestCases := func(commandLineArgs []string) []*tscInput { - aContent := `export const a = class { private p = 10; };` - return []*tscInput{ - { - subScenario: "dts errors with declaration enable changes with multiple files", - commandLineArgs: slices.Concat(commandLineArgs, []string{"--noEmit"}), - files: FileMap{ - "/home/src/projects/project/a.ts": aContent, - "/home/src/projects/project/b.ts": `export const b = 10;`, - "/home/src/projects/project/c.ts": strings.Replace(aContent, "a", "c", 1), - "/home/src/projects/project/d.ts": strings.Replace(aContent, "a", "d", 1), - "/home/src/projects/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "incremental": true, - } - } - `), - }, - cwd: "/home/src/projects/project", - edits: slices.Concat( - getTscNoEmitDtsChangesEdits(commandLineArgs), - []*tscEdit{ - { - caption: "Fix the another ", - edit: func(sys *testSys) { - sys.replaceFileText("/home/src/projects/project/c.ts", "private", "public") - }, - commandLineArgs: slices.Concat(commandLineArgs, []string{"--noEmit", "--declaration", "--declarationMap"}), - }, - }, - ), - }, - } - } - getTscNoEmitLoopTestCase := func(suffix string, commandLineArgs []string) *tscInput { - return &tscInput{ - subScenario: "does not go in loop when watching when no files are emitted" + suffix, - files: FileMap{ - "/user/username/projects/myproject/a.js": "", - "/user/username/projects/myproject/b.ts": "", - "/user/username/projects/myproject/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "allowJs": true, - "noEmit": true, - }, - }`), - }, - cwd: "/user/username/projects/myproject", - commandLineArgs: commandLineArgs, - edits: []*tscEdit{ - { - caption: "No change", - edit: func(sys *testSys) { - sys.writeFileNoError(`/user/username/projects/myproject/a.js`, sys.readFileNoError(`/user/username/projects/myproject/a.js`), false) - }, - }, - { - caption: "change", - edit: func(sys *testSys) { - sys.writeFileNoError(`/user/username/projects/myproject/a.js`, "const x = 10;", false) - }, - }, - }, - } - } - testCases := slices.Concat( - []*tscInput{ - { - subScenario: "when project has strict true", - files: FileMap{ - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "incremental": true, - "strict": true - } - }`), - "/home/src/workspaces/project/class1.ts": `export class class1 {}`, - }, - commandLineArgs: []string{"--noEmit"}, - edits: noChangeOnlyEdit, - }, - getTscNoEmitLoopTestCase("", []string{"-b", "-w", "-verbose"}), - getTscNoEmitLoopTestCase(" with incremental", []string{"-b", "-w", "-verbose", "--incremental"}), - }, - getTscNoEmitAndErrorsTestCases([]string{}), - getTscNoEmitAndErrorsTestCases([]string{"-b", "-v"}), - getTscNoEmitChangesTestCases([]string{}), - getTscNoEmitChangesTestCases([]string{"-b", "-v"}), - getTscNoEmitDtsChangesTestCases(), - getTscNoEmitDtsChangesMultiFileErrorsTestCases([]string{}), - getTscNoEmitDtsChangesMultiFileErrorsTestCases([]string{"-b", "-v"}), - getTscNoEmitAndErrorsWatchTestCases([]string{"-b", "-verbose", "-w"}), - ) - - for _, test := range testCases { - test.run(t, "noEmit") - } -} - -func TestTscNoEmitOnError(t *testing.T) { - t.Parallel() - type tscNoEmitOnErrorScenario struct { - subScenario string - mainErrorContent string - fixedErrorContent string - } - getTscNoEmitOnErrorFileMap := func(scenario *tscNoEmitOnErrorScenario, declaration bool, incremental bool) FileMap { - return FileMap{ - "/user/username/projects/noEmitOnError/tsconfig.json": stringtestutil.Dedent(fmt.Sprintf(` - { - "compilerOptions": { - "outDir": "./dev-build", - "declaration": %t, - "incremental": %t, - "noEmitOnError": true, - }, - }`, declaration, incremental)), - "/user/username/projects/noEmitOnError/shared/types/db.ts": stringtestutil.Dedent(` - export interface A { - name: string; - } - `), - "/user/username/projects/noEmitOnError/src/main.ts": scenario.mainErrorContent, - "/user/username/projects/noEmitOnError/src/other.ts": stringtestutil.Dedent(` - console.log("hi"); - export { } - `), - } - } - getTscNoEmitOnErrorTestCases := func(scenarios []*tscNoEmitOnErrorScenario, commandLineArgs []string) []*tscInput { - testCases := make([]*tscInput, 0, len(scenarios)*4) - for _, scenario := range scenarios { - edits := []*tscEdit{ - noChange, - { - caption: "Fix error", - edit: func(sys *testSys) { - sys.writeFileNoError("/user/username/projects/noEmitOnError/src/main.ts", scenario.fixedErrorContent, false) - }, - }, - noChange, - } - testCases = append( - testCases, - &tscInput{ - subScenario: scenario.subScenario, - files: getTscNoEmitOnErrorFileMap(scenario, false, false), - cwd: "/user/username/projects/noEmitOnError", - commandLineArgs: commandLineArgs, - edits: edits, - }, - &tscInput{ - subScenario: scenario.subScenario + " with declaration", - files: getTscNoEmitOnErrorFileMap(scenario, true, false), - cwd: "/user/username/projects/noEmitOnError", - commandLineArgs: commandLineArgs, - edits: edits, - }, - &tscInput{ - subScenario: scenario.subScenario + " with incremental", - files: getTscNoEmitOnErrorFileMap(scenario, false, true), - cwd: "/user/username/projects/noEmitOnError", - commandLineArgs: commandLineArgs, - edits: edits, - }, - &tscInput{ - subScenario: scenario.subScenario + " with declaration with incremental", - files: getTscNoEmitOnErrorFileMap(scenario, true, true), - cwd: "/user/username/projects/noEmitOnError", - commandLineArgs: commandLineArgs, - edits: edits, - }, - ) - } - return testCases - } - getTscWatchNoEmitOnErrorTestCases := func(scenarios []*tscNoEmitOnErrorScenario, commandLineArgs []string) []*tscInput { - var edits []*tscEdit - for _, scenario := range scenarios { - if edits != nil { - edits = append(edits, &tscEdit{ - caption: scenario.subScenario, - edit: func(sys *testSys) { - sys.writeFileNoError(`/user/username/projects/noEmitOnError/src/main.ts`, scenario.mainErrorContent, false) - }, - }) - } - edits = append(edits, - &tscEdit{ - caption: "No Change", - edit: func(sys *testSys) { - sys.writeFileNoError(`/user/username/projects/noEmitOnError/src/main.ts`, sys.readFileNoError(`/user/username/projects/noEmitOnError/src/main.ts`), false) - }, - }, - &tscEdit{ - caption: "Fix " + scenario.subScenario, - edit: func(sys *testSys) { - sys.writeFileNoError("/user/username/projects/noEmitOnError/src/main.ts", scenario.fixedErrorContent, false) - }, - }, - &tscEdit{ - caption: "No Change", - edit: func(sys *testSys) { - sys.writeFileNoError(`/user/username/projects/noEmitOnError/src/main.ts`, sys.readFileNoError(`/user/username/projects/noEmitOnError/src/main.ts`), false) - }, - }, - ) - } - return []*tscInput{ - { - subScenario: "noEmitOnError", - files: getTscNoEmitOnErrorFileMap(scenarios[0], false, false), - cwd: "/user/username/projects/noEmitOnError", - commandLineArgs: commandLineArgs, - edits: edits, - }, - { - subScenario: "noEmitOnError with declaration", - files: getTscNoEmitOnErrorFileMap(scenarios[0], true, false), - cwd: "/user/username/projects/noEmitOnError", - commandLineArgs: commandLineArgs, - edits: edits, - }, - { - subScenario: "noEmitOnError with incremental", - files: getTscNoEmitOnErrorFileMap(scenarios[0], false, true), - cwd: "/user/username/projects/noEmitOnError", - commandLineArgs: commandLineArgs, - edits: edits, - }, - { - subScenario: "noEmitOnError with declaration with incremental", - files: getTscNoEmitOnErrorFileMap(scenarios[0], true, true), - cwd: "/user/username/projects/noEmitOnError", - commandLineArgs: commandLineArgs, - edits: edits, - }, - } - } - scenarios := []*tscNoEmitOnErrorScenario{ - { - subScenario: "syntax errors", - mainErrorContent: stringtestutil.Dedent(` - import { A } from "../shared/types/db"; - const a = { - lastName: 'sdsd' - ; - `), - fixedErrorContent: stringtestutil.Dedent(` - import { A } from "../shared/types/db"; - const a = { - lastName: 'sdsd' - };`), - }, - { - subScenario: "semantic errors", - mainErrorContent: stringtestutil.Dedent(` - import { A } from "../shared/types/db"; - const a: string = 10;`), - fixedErrorContent: stringtestutil.Dedent(` - import { A } from "../shared/types/db"; - const a: string = "hello";`), - }, - { - subScenario: "dts errors", - mainErrorContent: stringtestutil.Dedent(` - import { A } from "../shared/types/db"; - export const a = class { private p = 10; }; - `), - fixedErrorContent: stringtestutil.Dedent(` - import { A } from "../shared/types/db"; - export const a = class { p = 10; }; - `), - }, - } - testCases := slices.Concat( - getTscNoEmitOnErrorTestCases(scenarios, []string{}), - getTscNoEmitOnErrorTestCases(scenarios, []string{"-b", "-v"}), - []*tscInput{ - { - subScenario: `when declarationMap changes`, - files: FileMap{ - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "noEmitOnError": true, - "declaration": true, - "composite": true, - }, - }`), - "/home/src/workspaces/project/a.ts": "const x = 10;", - "/home/src/workspaces/project/b.ts": "const y = 10;", - }, - edits: []*tscEdit{ - { - caption: "error and enable declarationMap", - edit: func(sys *testSys) { - sys.replaceFileText("/home/src/workspaces/project/a.ts", "x", "x: 20") - }, - commandLineArgs: []string{"--declarationMap"}, - }, - { - caption: "fix error declarationMap", - edit: func(sys *testSys) { - sys.replaceFileText("/home/src/workspaces/project/a.ts", "x: 20", "x") - }, - commandLineArgs: []string{"--declarationMap"}, - }, - }, - }, - { - subScenario: "file deleted before fixing error with noEmitOnError", - files: FileMap{ - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "outDir": "outDir", - "noEmitOnError": true, - }, - }`), - "/home/src/workspaces/project/file1.ts": `export const x: 30 = "hello";`, - "/home/src/workspaces/project/file2.ts": `export class D { }`, - }, - commandLineArgs: []string{"-i"}, - edits: []*tscEdit{ - { - caption: "delete file without error", - edit: func(sys *testSys) { - sys.removeNoError("/home/src/workspaces/project/file2.ts") - }, - }, - }, - }, - }, - getTscWatchNoEmitOnErrorTestCases(scenarios, []string{"-b", "-w", "-v"}), - ) - - for _, test := range testCases { - test.run(t, "noEmitOnError") - } -} - -func TestTscProjectReferences(t *testing.T) { - t.Parallel() - cases := []tscInput{ - { - subScenario: "when project references composite project with noEmit", - files: FileMap{ - "/home/src/workspaces/solution/utils/index.ts": "export const x = 10;", - "/home/src/workspaces/solution/utils/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "noEmit": true - } - }`), - "/home/src/workspaces/solution/project/index.ts": `import { x } from "../utils";`, - "/home/src/workspaces/solution/project/tsconfig.json": stringtestutil.Dedent(` - { - "references": [ - { "path": "../utils" }, - ], - }`), - }, - cwd: "/home/src/workspaces/solution", - commandLineArgs: []string{"--p", "project"}, - }, - { - subScenario: "when project references composite", - files: FileMap{ - "/home/src/workspaces/solution/utils/index.ts": "export const x = 10;", - "/home/src/workspaces/solution/utils/index.d.ts": "export declare const x = 10;", - "/home/src/workspaces/solution/utils/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true - } - }`), - "/home/src/workspaces/solution/project/index.ts": `import { x } from "../utils";`, - "/home/src/workspaces/solution/project/tsconfig.json": stringtestutil.Dedent(` - { - "references": [ - { "path": "../utils" }, - ], - }`), - }, - cwd: "/home/src/workspaces/solution", - commandLineArgs: []string{"--p", "project"}, - }, - { - subScenario: "when project reference is not built", - files: FileMap{ - "/home/src/workspaces/solution/utils/index.ts": "export const x = 10;", - "/home/src/workspaces/solution/utils/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true - } - }`), - "/home/src/workspaces/solution/project/index.ts": `import { x } from "../utils";`, - "/home/src/workspaces/solution/project/tsconfig.json": stringtestutil.Dedent(` - { - "references": [ - { "path": "../utils" }, - ], - }`), - }, - cwd: "/home/src/workspaces/solution", - commandLineArgs: []string{"--p", "project"}, - }, - { - subScenario: "when project contains invalid project reference", - files: FileMap{ - "/home/src/workspaces/solution/project/index.ts": `export const x = 10;`, - "/home/src/workspaces/solution/project/tsconfig.json": stringtestutil.Dedent(` - { - "references": [ - { "path": "../utils" }, - ], - }`), - }, - cwd: "/home/src/workspaces/solution", - commandLineArgs: []string{"--p", "project"}, - }, - { - subScenario: "default import interop uses referenced project settings", - files: FileMap{ - "/home/src/workspaces/project/node_modules/ambiguous-package/package.json": stringtestutil.Dedent(` - { - "name": "ambiguous-package" - }`), - "/home/src/workspaces/project/node_modules/ambiguous-package/index.d.ts": "export declare const ambiguous: number;", - "/home/src/workspaces/project/node_modules/esm-package/package.json": stringtestutil.Dedent(` - { - "name": "esm-package", - "type": "module" - }`), - "/home/src/workspaces/project/node_modules/esm-package/index.d.ts": "export declare const esm: number;", - "/home/src/workspaces/project/lib/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "declaration": true, - "rootDir": "src", - "outDir": "dist", - "module": "esnext", - "moduleResolution": "bundler", - }, - "include": ["src"], - }`), - "/home/src/workspaces/project/lib/src/a.ts": "export const a = 0;", - "/home/src/workspaces/project/lib/dist/a.d.ts": "export declare const a = 0;", - "/home/src/workspaces/project/app/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "module": "esnext", - "moduleResolution": "bundler", - "rootDir": "src", - "outDir": "dist", - }, - "include": ["src"], - "references": [ - { "path": "../lib" }, - ], - }`), - "/home/src/workspaces/project/app/src/local.ts": "export const local = 0;", - "/home/src/workspaces/project/app/src/index.ts": stringtestutil.Dedent(` - import local from "./local"; // Error - import esm from "esm-package"; // Error - import referencedSource from "../../lib/src/a"; // Error - import referencedDeclaration from "../../lib/dist/a"; // Error - import ambiguous from "ambiguous-package"; // Ok`), - }, - commandLineArgs: []string{"--p", "app", "--pretty", "false"}, - }, - { - subScenario: "referencing ambient const enum from referenced project with preserveConstEnums", - files: FileMap{ - "/home/src/workspaces/solution/utils/index.ts": "export const enum E { A = 1 }", - "/home/src/workspaces/solution/utils/index.d.ts": "export declare const enum E { A = 1 }", - "/home/src/workspaces/solution/utils/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "declaration": true, - "preserveConstEnums": true, - }, - }`), - "/home/src/workspaces/solution/project/index.ts": `import { E } from "../utils"; E.A;`, - "/home/src/workspaces/solution/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "isolatedModules": true, - }, - "references": [ - { "path": "../utils" }, - ], - }`), - }, - cwd: "/home/src/workspaces/solution", - commandLineArgs: []string{"--p", "project"}, - }, - { - subScenario: "importing const enum from referenced project with preserveConstEnums and verbatimModuleSyntax", - files: FileMap{ - "/home/src/workspaces/solution/preserve/index.ts": "export const enum E { A = 1 }", - "/home/src/workspaces/solution/preserve/index.d.ts": "export declare const enum E { A = 1 }", - "/home/src/workspaces/solution/preserve/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "declaration": true, - "preserveConstEnums": true, - }, - }`), - "/home/src/workspaces/solution/no-preserve/index.ts": "export const enum E { A = 1 }", - "/home/src/workspaces/solution/no-preserve/index.d.ts": "export declare const enum F { A = 1 }", - "/home/src/workspaces/solution/no-preserve/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "declaration": true, - "preserveConstEnums": false, - }, - }`), - "/home/src/workspaces/solution/project/index.ts": stringtestutil.Dedent(` - import { E } from "../preserve"; - import { F } from "../no-preserve"; - E.A; - F.A;`), - "/home/src/workspaces/solution/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "module": "preserve", - "verbatimModuleSyntax": true, - }, - "references": [ - { "path": "../preserve" }, - { "path": "../no-preserve" }, - ], - }`), - }, - cwd: "/home/src/workspaces/solution", - commandLineArgs: []string{"--p", "project", "--pretty", "false"}, - }, - { - subScenario: "rewriteRelativeImportExtensionsProjectReferences1", - files: FileMap{ - "/home/src/workspaces/packages/common/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "rootDir": "src", - "outDir": "dist", - "module": "nodenext" - } - }`), - "/home/src/workspaces/packages/common/package.json": stringtestutil.Dedent(` - { - "name": "common", - "version": "1.0.0", - "type": "module", - "exports": { - ".": { - "source": "./src/index.ts", - "default": "./dist/index.js" - } - } - }`), - "/home/src/workspaces/packages/common/src/index.ts": "export {};", - "/home/src/workspaces/packages/common/dist/index.d.ts": "export {};", - "/home/src/workspaces/packages/main/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "module": "nodenext", - "rewriteRelativeImportExtensions": true, - "rootDir": "src", - "outDir": "dist" - }, - "references": [ - { "path": "../common" } - ] - }`), - "/home/src/workspaces/packages/main/package.json": stringtestutil.Dedent(` - { - "type": "module" - }`), - "/home/src/workspaces/packages/main/src/index.ts": `import {} from "../../common/src/index.ts";`, - }, - cwd: "/home/src/workspaces", - commandLineArgs: []string{"-p", "packages/main", "--pretty", "false"}, - }, - { - subScenario: "rewriteRelativeImportExtensionsProjectReferences2", - files: FileMap{ - "/home/src/workspaces/solution/src/tsconfig-base.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "module": "nodenext", - "composite": true, - "rootDir": ".", - "outDir": "../dist", - "rewriteRelativeImportExtensions": true - } - }`), - "/home/src/workspaces/solution/src/compiler/tsconfig.json": stringtestutil.Dedent(` - { - "extends": "../tsconfig-base.json", - "compilerOptions": {} - }`), - "/home/src/workspaces/solution/src/compiler/parser.ts": "export {};", - "/home/src/workspaces/solution/dist/compiler/parser.d.ts": "export {};", - "/home/src/workspaces/solution/src/services/tsconfig.json": stringtestutil.Dedent(` - { - "extends": "../tsconfig-base.json", - "compilerOptions": {}, - "references": [ - { "path": "../compiler" } - ] - }`), - "/home/src/workspaces/solution/src/services/services.ts": `import {} from "../compiler/parser.ts";`, - }, - cwd: "/home/src/workspaces/solution", - commandLineArgs: []string{"--p", "src/services", "--pretty", "false"}, - }, - { - subScenario: "rewriteRelativeImportExtensionsProjectReferences3", - files: FileMap{ - "/home/src/workspaces/solution/src/tsconfig-base.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "module": "nodenext", - "composite": true, - "rewriteRelativeImportExtensions": true - } - }`), - "/home/src/workspaces/solution/src/compiler/tsconfig.json": stringtestutil.Dedent(` - { - "extends": "../tsconfig-base.json", - "compilerOptions": { - "rootDir": ".", - "outDir": "../../dist/compiler" - } - }`), - "/home/src/workspaces/solution/src/compiler/parser.ts": "export {};", - "/home/src/workspaces/solution/dist/compiler/parser.d.ts": "export {};", - "/home/src/workspaces/solution/src/services/tsconfig.json": stringtestutil.Dedent(` - { - "extends": "../tsconfig-base.json", - "compilerOptions": { - "rootDir": ".", - "outDir": "../../dist/services" - }, - "references": [ - { "path": "../compiler" } - ] - }`), - "/home/src/workspaces/solution/src/services/services.ts": `import {} from "../compiler/parser.ts";`, - }, - cwd: "/home/src/workspaces/solution", - commandLineArgs: []string{"--p", "src/services", "--pretty", "false"}, - }, - { - subScenario: "default setup was created correctly", - files: FileMap{ - "/home/src/workspaces/project/primary/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "outDir": "bin", - } - }`), - "/home/src/workspaces/project/primary/a.ts": "export { };", - "/home/src/workspaces/project/secondary/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "outDir": "bin", - }, - "references": [{ - "path": "../primary" - }] - }`), - "/home/src/workspaces/project/secondary/b.ts": `import * as mod_1 from "../primary/a";`, - }, - commandLineArgs: []string{"--p", "primary/tsconfig.json"}, - }, - { - subScenario: "errors when declaration = false", - files: FileMap{ - "/home/src/workspaces/project/primary/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "outDir": "bin", - "declaration": false - } - }`), - "/home/src/workspaces/project/primary/a.ts": "export { };", - }, - commandLineArgs: []string{"--p", "primary/tsconfig.json"}, - }, - { - subScenario: "errors when the referenced project doesnt have composite", - files: FileMap{ - "/home/src/workspaces/project/primary/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": false, - "outDir": "bin", - } - }`), - "/home/src/workspaces/project/primary/a.ts": "export { };", - "/home/src/workspaces/project/reference/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "outDir": "bin", - }, - "files": [ "b.ts" ], - "references": [ { "path": "../primary" } ] - }`), - "/home/src/workspaces/project/reference/b.ts": `import * as mod_1 from "../primary/a";`, - }, - commandLineArgs: []string{"--p", "reference/tsconfig.json"}, - }, - { - subScenario: "does not error when the referenced project doesnt have composite if its a container project", - files: FileMap{ - "/home/src/workspaces/project/primary/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": false, - "outDir": "bin", - } - }`), - "/home/src/workspaces/project/primary/a.ts": "export { };", - "/home/src/workspaces/project/reference/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "outDir": "bin", - }, - "files": [ ], - "references": [{ - "path": "../primary" - }] - }`), - "/home/src/workspaces/project/reference/b.ts": `import * as mod_1 from "../primary/a";`, - }, - commandLineArgs: []string{"--p", "reference/tsconfig.json"}, - }, - { - subScenario: "errors when the file list is not exhaustive", - files: FileMap{ - "/home/src/workspaces/project/primary/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "outDir": "bin", - }, - "files": [ "a.ts" ] - }`), - "/home/src/workspaces/project/primary/a.ts": "import * as b from './b'", - "/home/src/workspaces/project/primary/b.ts": "export {}", - }, - commandLineArgs: []string{"--p", "primary/tsconfig.json"}, - }, - { - subScenario: "errors when the referenced project doesnt exist", - files: FileMap{ - "/home/src/workspaces/project/primary/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "outDir": "bin", - }, - "references": [{ - "path": "../foo" - }] - }`), - "/home/src/workspaces/project/primary/a.ts": "export { };", - }, - commandLineArgs: []string{"--p", "primary/tsconfig.json"}, - }, - { - subScenario: "redirects to the output dts file", - files: FileMap{ - "/home/src/workspaces/project/alpha/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "outDir": "bin", - } - }`), - "/home/src/workspaces/project/alpha/a.ts": "export const m: number = 3;", - "/home/src/workspaces/project/alpha/bin/a.d.ts": "export { };", - "/home/src/workspaces/project/beta/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "outDir": "bin", - }, - "references": [ { "path": "../alpha" } ] - }`), - "/home/src/workspaces/project/beta/b.ts": "import { m } from '../alpha/a'", - }, - commandLineArgs: []string{"--p", "beta/tsconfig.json", "--explainFiles"}, - }, - { - subScenario: "issues a nice error when the input file is missing", - files: FileMap{ - "/home/src/workspaces/project/alpha/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "outDir": "bin", - }, - "references": [] - }`), - "/home/src/workspaces/project/alpha/a.ts": "export const m: number = 3;", - "/home/src/workspaces/project/beta/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "outDir": "bin", - }, - "references": [ { "path": "../alpha" } ] - }`), - "/home/src/workspaces/project/beta/b.ts": "import { m } from '../alpha/a'", - }, - commandLineArgs: []string{"--p", "beta/tsconfig.json"}, - }, - { - subScenario: "issues a nice error when the input file is missing when module reference is not relative", - files: FileMap{ - "/home/src/workspaces/project/alpha/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "outDir": "bin", - } - }`), - "/home/src/workspaces/project/alpha/a.ts": "export const m: number = 3;", - "/home/src/workspaces/project/beta/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "outDir": "bin", - "paths": { - "@alpha/*": ["../alpha/*"], - }, - }, - "references": [ { "path": "../alpha" } ] - }`), - "/home/src/workspaces/project/beta/b.ts": "import { m } from '@alpha/a'", - }, - commandLineArgs: []string{"--p", "beta/tsconfig.json"}, - }, - { - subScenario: "doesnt infer the rootDir from source paths", - files: FileMap{ - "/home/src/workspaces/project/alpha/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "outDir": "bin", - }, - "references": [] - }`), - "/home/src/workspaces/project/alpha/src/a.ts": "export const m: number = 3;", - }, - commandLineArgs: []string{"--p", "alpha/tsconfig.json"}, - }, - { - // !!! sheetal rootDir error not reported - subScenario: "errors when a file is outside the rootdir", - files: FileMap{ - "/home/src/workspaces/project/alpha/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "outDir": "bin", - }, - "references": [] - }`), - "/home/src/workspaces/project/alpha/src/a.ts": "import * as b from '../../beta/b'", - "/home/src/workspaces/project/beta/b.ts": "export { }", - }, - commandLineArgs: []string{"--p", "alpha/tsconfig.json"}, - }, - } - - for _, c := range cases { - c.run(t, "projectReferences") - } -} - -func TestTypeAcquisition(t *testing.T) { - t.Parallel() - (&tscInput{ - subScenario: "parse tsconfig with typeAcquisition", - files: FileMap{ - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "noEmit": true, - }, - "typeAcquisition": { - "enable": true, - "include": ["0.d.ts", "1.d.ts"], - "exclude": ["0.js", "1.js"], - "disableFilenameBasedTypeAcquisition": true, - }, - }`), - }, - commandLineArgs: []string{}, - }).run(t, "typeAcquisition") -} diff --git a/kitcom/internal/tsgo/execute/tsctests/tscbuild_test.go b/kitcom/internal/tsgo/execute/tsctests/tscbuild_test.go deleted file mode 100644 index d07fab9..0000000 --- a/kitcom/internal/tsgo/execute/tsctests/tscbuild_test.go +++ /dev/null @@ -1,3934 +0,0 @@ -package tsctests - -import ( - "fmt" - "slices" - "strings" - "testing" - "time" - - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/core" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/testutil/harnessutil" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/testutil/stringtestutil" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tsoptions" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/vfs/vfstest" - "gotest.tools/v3/assert" -) - -func TestBuildCommandLine(t *testing.T) { - t.Parallel() - getBuildCommandLineDifferentOptionsMap := func(optionName string) FileMap { - return FileMap{ - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(fmt.Sprintf(` - { - "compilerOptions": { - "%s": true - } - }`, optionName)), - "/home/src/workspaces/project/a.ts": `export const a = 10;const aLocal = 10;`, - "/home/src/workspaces/project/b.ts": `export const b = 10;const bLocal = 10;`, - "/home/src/workspaces/project/c.ts": `import { a } from "./a";export const c = a;`, - "/home/src/workspaces/project/d.ts": `import { b } from "./b";export const d = b;`, - } - } - getBuildCommandLineEmitDeclarationOnlyMap := func(options []string) FileMap { - compilerOptionsStr := strings.Join(core.Map(options, func(opt string) string { - return fmt.Sprintf(`"%s": true`, opt) - }), ", ") - return FileMap{ - "/home/src/workspaces/solution/project1/src/tsconfig.json": stringtestutil.Dedent(fmt.Sprintf(` - { - "compilerOptions": { %s } - }`, compilerOptionsStr)), - "/home/src/workspaces/solution/project1/src/a.ts": `export const a = 10;const aLocal = 10;`, - "/home/src/workspaces/solution/project1/src/b.ts": `export const b = 10;const bLocal = 10;`, - "/home/src/workspaces/solution/project1/src/c.ts": `import { a } from "./a";export const c = a;`, - "/home/src/workspaces/solution/project1/src/d.ts": `import { b } from "./b";export const d = b;`, - "/home/src/workspaces/solution/project2/src/tsconfig.json": stringtestutil.Dedent(fmt.Sprintf(` - { - "compilerOptions": { %s }, - "references": [{ "path": "../../project1/src" }] - }`, compilerOptionsStr)), - "/home/src/workspaces/solution/project2/src/e.ts": `export const e = 10;`, - "/home/src/workspaces/solution/project2/src/f.ts": `import { a } from "../../project1/src/a"; export const f = a;`, - "/home/src/workspaces/solution/project2/src/g.ts": `import { b } from "../../project1/src/b"; export const g = b;`, - } - } - getBuildCommandLineEmitDeclarationOnlyTestCases := func(options []string, suffix string) []*tscInput { - return []*tscInput{ - { - subScenario: "emitDeclarationOnly on commandline" + suffix, - files: getBuildCommandLineEmitDeclarationOnlyMap(options), - cwd: "/home/src/workspaces/solution", - commandLineArgs: []string{"--b", "project2/src", "--verbose", "--emitDeclarationOnly"}, - edits: []*tscEdit{ - noChange, - { - caption: "local change", - edit: func(sys *testSys) { - sys.appendFile("/home/src/workspaces/solution/project1/src/a.ts", "const aa = 10;") - }, - }, - { - caption: "non local change", - edit: func(sys *testSys) { - sys.appendFile("/home/src/workspaces/solution/project1/src/a.ts", "export const aaa = 10;") - }, - }, - { - caption: "emit js files", - commandLineArgs: []string{"--b", "project2/src", "--verbose"}, - }, - noChange, - { - caption: "js emit with change without emitDeclarationOnly", - edit: func(sys *testSys) { - sys.appendFile("/home/src/workspaces/solution/project1/src/b.ts", "const alocal = 10;") - }, - commandLineArgs: []string{"--b", "project2/src", "--verbose"}, - }, - { - caption: "local change", - edit: func(sys *testSys) { - sys.appendFile("/home/src/workspaces/solution/project1/src/b.ts", "const aaaa = 10;") - }, - }, - { - caption: "non local change", - edit: func(sys *testSys) { - sys.appendFile("/home/src/workspaces/solution/project1/src/b.ts", "export const aaaaa = 10;") - }, - }, - { - caption: "js emit with change without emitDeclarationOnly", - edit: func(sys *testSys) { - sys.appendFile("/home/src/workspaces/solution/project1/src/b.ts", "export const a2 = 10;") - }, - commandLineArgs: []string{"--b", "project2/src", "--verbose"}, - }, - }, - }, - { - subScenario: "emitDeclarationOnly false on commandline" + suffix, - files: getBuildCommandLineEmitDeclarationOnlyMap(slices.Concat(options, []string{"emitDeclarationOnly"})), - cwd: "/home/src/workspaces/solution", - commandLineArgs: []string{"--b", "project2/src", "--verbose"}, - edits: []*tscEdit{ - noChange, - { - caption: "change", - edit: func(sys *testSys) { - sys.appendFile("/home/src/workspaces/solution/project1/src/a.ts", "const aa = 10;") - }, - }, - { - caption: "emit js files", - commandLineArgs: []string{"--b", "project2/src", "--verbose", "--emitDeclarationOnly", "false"}, - }, - noChange, - { - caption: "no change run with js emit", - commandLineArgs: []string{"--b", "project2/src", "--verbose", "--emitDeclarationOnly", "false"}, - }, - { - caption: "js emit with change", - edit: func(sys *testSys) { - sys.appendFile("/home/src/workspaces/solution/project1/src/b.ts", "const blocal = 10;") - }, - commandLineArgs: []string{"--b", "project2/src", "--verbose", "--emitDeclarationOnly", "false"}, - }, - }, - }, - } - } - testCases := slices.Concat( - []*tscInput{ - { - subScenario: "help", - files: FileMap{}, - commandLineArgs: []string{"--build", "--help"}, - }, - { - subScenario: "different options", - files: getBuildCommandLineDifferentOptionsMap("composite"), - commandLineArgs: []string{"--build", "--verbose"}, - edits: []*tscEdit{ - { - caption: "with sourceMap", - commandLineArgs: []string{"--build", "--verbose", "--sourceMap"}, - }, - { - caption: "should re-emit only js so they dont contain sourcemap", - }, - { - caption: "with declaration should not emit anything", - commandLineArgs: []string{"--build", "--verbose", "--declaration"}, - }, - noChange, - { - caption: "with declaration and declarationMap", - commandLineArgs: []string{"--build", "--verbose", "--declaration", "--declarationMap"}, - }, - { - caption: "should re-emit only dts so they dont contain sourcemap", - }, - { - caption: "with emitDeclarationOnly should not emit anything", - commandLineArgs: []string{"--build", "--verbose", "--emitDeclarationOnly"}, - }, - noChange, - { - caption: "local change", - edit: func(sys *testSys) { - sys.replaceFileText("/home/src/workspaces/project/a.ts", "Local = 1", "Local = 10") - }, - }, - { - caption: "with declaration should not emit anything", - commandLineArgs: []string{"--build", "--verbose", "--declaration"}, - }, - { - caption: "with inlineSourceMap", - commandLineArgs: []string{"--build", "--verbose", "--inlineSourceMap"}, - }, - { - caption: "with sourceMap", - commandLineArgs: []string{"--build", "--verbose", "--sourceMap"}, - }, - }, - }, - { - subScenario: "different options with incremental", - files: getBuildCommandLineDifferentOptionsMap("incremental"), - commandLineArgs: []string{"--build", "--verbose"}, - edits: []*tscEdit{ - { - caption: "with sourceMap", - commandLineArgs: []string{"--build", "--verbose", "--sourceMap"}, - }, - { - caption: "should re-emit only js so they dont contain sourcemap", - }, - { - caption: "with declaration, emit Dts and should not emit js", - commandLineArgs: []string{"--build", "--verbose", "--declaration"}, - }, - { - caption: "with declaration and declarationMap", - commandLineArgs: []string{"--build", "--verbose", "--declaration", "--declarationMap"}, - }, - noChange, - { - caption: "local change", - edit: func(sys *testSys) { - sys.replaceFileText("/home/src/workspaces/project/a.ts", "Local = 1", "Local = 10") - }, - }, - { - caption: "with declaration and declarationMap", - commandLineArgs: []string{"--build", "--verbose", "--declaration", "--declarationMap"}, - }, - noChange, - { - caption: "with inlineSourceMap", - commandLineArgs: []string{"--build", "--verbose", "--inlineSourceMap"}, - }, - { - caption: "with sourceMap", - commandLineArgs: []string{"--build", "--verbose", "--sourceMap"}, - }, - { - caption: "emit js files", - }, - { - caption: "with declaration and declarationMap", - commandLineArgs: []string{"--build", "--verbose", "--declaration", "--declarationMap"}, - }, - { - caption: "with declaration and declarationMap, should not re-emit", - commandLineArgs: []string{"--build", "--verbose", "--declaration", "--declarationMap"}, - }, - }, - }, - }, - getBuildCommandLineEmitDeclarationOnlyTestCases([]string{"composite"}, ""), - getBuildCommandLineEmitDeclarationOnlyTestCases([]string{"incremental", "declaration"}, " with declaration and incremental"), - getBuildCommandLineEmitDeclarationOnlyTestCases([]string{"declaration"}, " with declaration"), - ) - - for _, test := range testCases { - test.run(t, "commandLine") - } -} - -func TestBuildClean(t *testing.T) { - t.Parallel() - testCases := []*tscInput{ - { - subScenario: "file name and output name clashing", - files: FileMap{ - "/home/src/workspaces/solution/index.js": "", - "/home/src/workspaces/solution/bar.ts": "", - "/home/src/workspaces/solution/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { "allowJs": true } - }`), - }, - cwd: "/home/src/workspaces/solution", - commandLineArgs: []string{"--b", "--clean"}, - }, - { - subScenario: "tsx with dts emit", - files: FileMap{ - "/home/src/workspaces/solution/project/src/main.tsx": "export const x = 10;", - "/home/src/workspaces/solution/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { "declaration": true }, - "include": ["src/**/*.tsx", "src/**/*.ts"] - }`), - }, - cwd: "/home/src/workspaces/solution", - commandLineArgs: []string{"--b", "project", "-v", "--explainFiles"}, - edits: []*tscEdit{ - noChange, - { - caption: "clean build", - commandLineArgs: []string{"-b", "project", "--clean"}, - }, - }, - }, - } - - for _, test := range testCases { - test.run(t, "clean") - } -} - -func TestBuildConfigFileErrors(t *testing.T) { - t.Parallel() - testCases := []*tscInput{ - { - subScenario: "when tsconfig extends the missing file", - files: FileMap{ - "/home/src/workspaces/project/tsconfig.first.json": stringtestutil.Dedent(` - { - "extends": "./foobar.json", - "compilerOptions": { - "composite": true - } - }`), - "/home/src/workspaces/project/tsconfig.second.json": stringtestutil.Dedent(` - { - "extends": "./foobar.json", - "compilerOptions": { - "composite": true - } - }`), - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true - }, - "references": [ - { "path": "./tsconfig.first.json" }, - { "path": "./tsconfig.second.json" } - ] - }`), - }, - commandLineArgs: []string{"--b"}, - }, - { - subScenario: "reports syntax errors in config file", - files: FileMap{ - "/home/src/workspaces/project/a.ts": "export function foo() { }", - "/home/src/workspaces/project/b.ts": "export function bar() { }", - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - }, - "files": [ - "a.ts" - "b.ts" - ] - }`), - }, - commandLineArgs: []string{"--b"}, - edits: []*tscEdit{ - { - caption: "reports syntax errors after change to config file", - edit: func(sys *testSys) { - sys.replaceFileText("/home/src/workspaces/project/tsconfig.json", ",", `, "declaration": true`) - }, - }, - { - caption: "reports syntax errors after change to ts file", - edit: func(sys *testSys) { - sys.appendFile("/home/src/workspaces/project/a.ts", "export function fooBar() { }") - }, - }, - noChange, - { - caption: "builds after fixing config file errors", - edit: func(sys *testSys) { - sys.writeFileNoError("/home/src/workspaces/project/tsconfig.json", stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, "declaration": true - }, - "files": [ - "a.ts", - "b.ts" - ] - }`), false) - }, - }, - }, - }, - { - subScenario: "missing config file", - files: FileMap{}, - commandLineArgs: []string{"--b", "bogus.json"}, - }, - { - subScenario: "reports syntax errors in config file", - files: FileMap{ - "/home/src/workspaces/project/a.ts": "export function foo() { }", - "/home/src/workspaces/project/b.ts": "export function bar() { }", - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - }, - "files": [ - "a.ts" - "b.ts" - ] - }`), - }, - commandLineArgs: []string{"--b", "-w"}, - edits: []*tscEdit{ - { - caption: "reports syntax errors after change to config file", - edit: func(sys *testSys) { - sys.replaceFileText("/home/src/workspaces/project/tsconfig.json", ",", `, "declaration": true`) - }, - }, - { - caption: "reports syntax errors after change to ts file", - edit: func(sys *testSys) { - sys.appendFile("/home/src/workspaces/project/a.ts", "export function fooBar() { }") - }, - }, - { - caption: "reports error when there is no change to tsconfig file", - edit: func(sys *testSys) { - sys.replaceFileText("/home/src/workspaces/project/tsconfig.json", "", "") - }, - }, - { - caption: "builds after fixing config file errors", - edit: func(sys *testSys) { - sys.writeFileNoError("/home/src/workspaces/project/tsconfig.json", stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, "declaration": true - }, - "files": [ - "a.ts", - "b.ts" - ] - }`), false) - }, - }, - }, - }, - } - - for _, test := range testCases { - test.run(t, "configFileErrors") - } -} - -func TestBuildDemoProject(t *testing.T) { - t.Parallel() - - getBuildDemoFileMap := func(modify func(FileMap)) FileMap { - files := FileMap{ - "/user/username/projects/demo/animals/animal.ts": stringtestutil.Dedent(` - export type Size = "small" | "medium" | "large"; - export default interface Animal { - size: Size; - } - `), - "/user/username/projects/demo/animals/dog.ts": stringtestutil.Dedent(` - import Animal from '.'; - import { makeRandomName } from '../core/utilities'; - - export interface Dog extends Animal { - woof(): void; - name: string; - } - - export function createDog(): Dog { - return ({ - size: "medium", - woof: function(this: Dog) { - console.log(` + "`" + `${ this.name } says "Woof"!` + "`" + `); - }, - name: makeRandomName() - }); - } - `), - "/user/username/projects/demo/animals/index.ts": stringtestutil.Dedent(` - import Animal from './animal'; - - export default Animal; - import { createDog, Dog } from './dog'; - export { createDog, Dog }; - `), - "/user/username/projects/demo/animals/tsconfig.json": stringtestutil.Dedent(` - { - "extends": "../tsconfig-base.json", - "compilerOptions": { - "outDir": "../lib/animals", - "rootDir": "." - }, - "references": [ - { "path": "../core" } - ] - } - `), - "/user/username/projects/demo/core/utilities.ts": stringtestutil.Dedent(` - - export function makeRandomName() { - return "Bob!?! "; - } - - export function lastElementOf(arr: T[]): T | undefined { - if (arr.length === 0) return undefined; - return arr[arr.length - 1]; - } - `), - "/user/username/projects/demo/core/tsconfig.json": stringtestutil.Dedent(` - { - "extends": "../tsconfig-base.json", - "compilerOptions": { - "outDir": "../lib/core", - "rootDir": "." - }, - } - `), - "/user/username/projects/demo/zoo/zoo.ts": stringtestutil.Dedent(` - import { Dog, createDog } from '../animals/index'; - - export function createZoo(): Array { - return [ - createDog() - ]; - } - `), - "/user/username/projects/demo/zoo/tsconfig.json": stringtestutil.Dedent(` - { - "extends": "../tsconfig-base.json", - "compilerOptions": { - "outDir": "../lib/zoo", - "rootDir": "." - }, - "references": [ - { - "path": "../animals" - } - ] - } - `), - "/user/username/projects/demo/tsconfig-base.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "declaration": true, - "target": "es5", - "module": "commonjs", - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "composite": true, - }, - } - `), - "/user/username/projects/demo/tsconfig.json": stringtestutil.Dedent(` - { - "files": [], - "references": [ - { - "path": "./core" - }, - { - "path": "./animals", - }, - { - "path": "./zoo", - }, - ], - } - `), - } - if modify != nil { - modify(files) - } - return files - } - testCases := []*tscInput{ - { - subScenario: "in master branch with everything setup correctly and reports no error", - files: getBuildDemoFileMap(nil), - cwd: "/user/username/projects/demo", - commandLineArgs: []string{"--b", "--verbose"}, - edits: noChangeOnlyEdit, - }, - { - subScenario: "in circular branch reports the error about it by stopping build", - files: getBuildDemoFileMap(func(files FileMap) { - files["/user/username/projects/demo/core/tsconfig.json"] = stringtestutil.Dedent(` - { - "extends": "../tsconfig-base.json", - "compilerOptions": { - "outDir": "../lib/core", - "rootDir": "." - }, - "references": [ - { - "path": "../zoo", - } - ] - } - `) - }), - cwd: "/user/username/projects/demo", - commandLineArgs: []string{"--b", "--verbose"}, - }, - { - // !!! sheetal - this has missing errors from strada about files not in rootDir (3) - subScenario: "in bad-ref branch reports the error about files not in rootDir at the import location", - files: getBuildDemoFileMap(func(files FileMap) { - files["/user/username/projects/demo/core/utilities.ts"] = `import * as A from '../animals' -` + files["/user/username/projects/demo/core/utilities.ts"].(string) - }), - cwd: "/user/username/projects/demo", - commandLineArgs: []string{"--b", "--verbose"}, - }, - { - subScenario: "in circular is set in the reference", - files: getBuildDemoFileMap(func(files FileMap) { - files["/user/username/projects/demo/a/tsconfig.json"] = stringtestutil.Dedent(` - { - "extends": "../tsconfig-base.json", - "compilerOptions": { - "outDir": "../lib/a", - "rootDir": "." - }, - "references": [ - { - "path": "../b", - "circular": true - } - ] - }`) - files["/user/username/projects/demo/b/tsconfig.json"] = stringtestutil.Dedent(` - { - "extends": "../tsconfig-base.json", - "compilerOptions": { - "outDir": "../lib/b", - "rootDir": "." - }, - "references": [ - { - "path": "../a", - } - ] - }`) - files["/user/username/projects/demo/a/index.ts"] = "export const a = 10;" - files["/user/username/projects/demo/b/index.ts"] = "export const b = 10;" - files["/user/username/projects/demo/tsconfig.json"] = stringtestutil.Dedent(` - { - "files": [], - "references": [ - { - "path": "./core" - }, - { - "path": "./animals", - }, - { - "path": "./zoo", - }, - { - "path": "./a", - }, - { - "path": "./b", - }, - ], - }`) - }), - cwd: "/user/username/projects/demo", - commandLineArgs: []string{"--b", "--verbose"}, - }, - { - subScenario: "updates with circular reference", - files: getBuildDemoFileMap(func(files FileMap) { - files["/user/username/projects/demo/core/tsconfig.json"] = stringtestutil.Dedent(` - { - "extends": "../tsconfig-base.json", - "compilerOptions": { - "outDir": "../lib/core", - "rootDir": "." - }, - "references": [ - { - "path": "../zoo", - } - ] - } - `) - }), - cwd: "/user/username/projects/demo", - commandLineArgs: []string{"--b", "-w", "--verbose"}, - edits: []*tscEdit{ - { - caption: "Fix error", - edit: func(sys *testSys) { - sys.writeFileNoError("/user/username/projects/demo/core/tsconfig.json", stringtestutil.Dedent(` - { - "extends": "../tsconfig-base.json", - "compilerOptions": { - "outDir": "../lib/core", - "rootDir": "." - }, - } - `), false) - }, - }, - }, - }, - { - // !!! sheetal - this has missing errors from strada about files not in rootDir (3) - subScenario: "updates with bad reference", - files: getBuildDemoFileMap(func(files FileMap) { - files["/user/username/projects/demo/core/utilities.ts"] = `import * as A from '../animals' -` + files["/user/username/projects/demo/core/utilities.ts"].(string) - }), - cwd: "/user/username/projects/demo", - commandLineArgs: []string{"--b", "-w", "--verbose"}, - edits: []*tscEdit{ - { - caption: "Prepend a line", - edit: func(sys *testSys) { - sys.prependFile("/user/username/projects/demo/core/utilities.ts", "\n") - }, - }, - }, - }, - } - - for _, test := range testCases { - test.run(t, "demo") - } -} - -func TestBuildEmitDeclarationOnly(t *testing.T) { - t.Parallel() - getBuildEmitDeclarationOnlyImportFileMap := func(declarationMap bool, circularRef bool) FileMap { - files := FileMap{ - "/home/src/workspaces/project/src/a.ts": stringtestutil.Dedent(` - import { B } from "./b"; - - export interface A { - b: B; - } - `), - "/home/src/workspaces/project/src/b.ts": stringtestutil.Dedent(` - import { C } from "./c"; - - export interface B { - b: C; - } - `), - "/home/src/workspaces/project/src/c.ts": stringtestutil.Dedent(` - import { A } from "./a"; - - export interface C { - a: A; - } - `), - "/home/src/workspaces/project/src/index.ts": stringtestutil.Dedent(` - export { A } from "./a"; - export { B } from "./b"; - export { C } from "./c"; - `), - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(fmt.Sprintf(` - { - "compilerOptions": { - "incremental": true, - "target": "es5", - "module": "commonjs", - "declaration": true, - "declarationMap": %t, - "sourceMap": true, - "outDir": "./lib", - "composite": true, - "strict": true, - "esModuleInterop": true, - "alwaysStrict": true, - "rootDir": "src", - "emitDeclarationOnly": true, - }, - }`, declarationMap)), - } - if !circularRef { - delete(files, "/home/src/workspaces/project/src/index.ts") - files["/home/src/workspaces/project/src/a.ts"] = stringtestutil.Dedent(` - export class B { prop = "hello"; } - - export interface A { - b: B; - } - `) - } - return files - } - getBuildEmitDeclarationOnlyTestCase := func(declarationMap bool) *tscInput { - return &tscInput{ - subScenario: `only dts output in circular import project with emitDeclarationOnly` + core.IfElse(declarationMap, " and declarationMap", ""), - files: getBuildEmitDeclarationOnlyImportFileMap(declarationMap, true), - commandLineArgs: []string{"--b", "--verbose"}, - edits: []*tscEdit{ - { - caption: "incremental-declaration-changes", - edit: func(sys *testSys) { - sys.replaceFileText("/home/src/workspaces/project/src/a.ts", "b: B;", "b: B; foo: any;") - }, - }, - }, - } - } - testCases := []*tscInput{ - getBuildEmitDeclarationOnlyTestCase(false), - getBuildEmitDeclarationOnlyTestCase(true), - { - subScenario: `only dts output in non circular imports project with emitDeclarationOnly`, - files: getBuildEmitDeclarationOnlyImportFileMap(true, false), - commandLineArgs: []string{"--b", "--verbose"}, - edits: []*tscEdit{ - { - caption: "incremental-declaration-doesnt-change", - edit: func(sys *testSys) { - sys.replaceFileText( - "/home/src/workspaces/project/src/a.ts", - "export interface A {", - stringtestutil.Dedent(` - class C { } - export interface A {`), - ) - }, - }, - { - caption: "incremental-declaration-changes", - edit: func(sys *testSys) { - sys.replaceFileText("/home/src/workspaces/project/src/a.ts", "b: B;", "b: B; foo: any;") - }, - }, - }, - }, - } - - for _, test := range testCases { - test.run(t, "emitDeclarationOnly") - } -} - -func TestBuildFileDelete(t *testing.T) { - t.Parallel() - testCases := []*tscInput{ - { - subScenario: "detects deleted file", - files: FileMap{ - "/home/src/workspaces/solution/child/child.ts": stringtestutil.Dedent(` - import { child2 } from "../child/child2"; - export function child() { - child2(); - } - `), - "/home/src/workspaces/solution/child/child2.ts": stringtestutil.Dedent(` - export function child2() { - } - `), - "/home/src/workspaces/solution/child/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { "composite": true } - } - `), - "/home/src/workspaces/solution/main/main.ts": stringtestutil.Dedent(` - import { child } from "../child/child"; - export function main() { - child(); - } - `), - "/home/src/workspaces/solution/main/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { "composite": true }, - "references": [{ "path": "../child" }], - } - `), - }, - cwd: "/home/src/workspaces/solution", - commandLineArgs: []string{"--b", "main/tsconfig.json", "-v", "--traceResolution", "--explainFiles"}, - edits: []*tscEdit{ - { - caption: "delete child2 file", - edit: func(sys *testSys) { - sys.removeNoError("/home/src/workspaces/solution/child/child2.ts") - sys.removeNoError("/home/src/workspaces/solution/child/child2.js") - sys.removeNoError("/home/src/workspaces/solution/child/child2.d.ts") - }, - }, - }, - }, - { - subScenario: "deleted file without composite", - files: FileMap{ - "/home/src/workspaces/solution/child/child.ts": stringtestutil.Dedent(` - import { child2 } from "../child/child2"; - export function child() { - child2(); - } - `), - "/home/src/workspaces/solution/child/child2.ts": stringtestutil.Dedent(` - export function child2() { - } - `), - "/home/src/workspaces/solution/child/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { } - } - `), - }, - cwd: "/home/src/workspaces/solution", - commandLineArgs: []string{"--b", "child/tsconfig.json", "-v", "--traceResolution", "--explainFiles"}, - edits: []*tscEdit{ - { - caption: "delete child2 file", - edit: func(sys *testSys) { - sys.removeNoError("/home/src/workspaces/solution/child/child2.ts") - sys.removeNoError("/home/src/workspaces/solution/child/child2.js") - }, - }, - }, - }, - } - - for _, test := range testCases { - test.run(t, "fileDelete") - } -} - -func TestBuildInferredTypeFromTransitiveModule(t *testing.T) { - t.Parallel() - getBuildInferredTypeFromTransitiveModuleMap := func(isolatedModules bool, lazyExtraContents string) FileMap { - return FileMap{ - "/home/src/workspaces/project/bar.ts": stringtestutil.Dedent(` - interface RawAction { - (...args: any[]): Promise | void; - } - interface ActionFactory { - (target: T): T; - } - declare function foo(): ActionFactory; - export default foo()(function foobar(param: string): void { - }); - `), - "/home/src/workspaces/project/bundling.ts": stringtestutil.Dedent(` - export class LazyModule { - constructor(private importCallback: () => Promise) {} - } - - export class LazyAction< - TAction extends (...args: any[]) => any, - TModule - > { - constructor(_lazyModule: LazyModule, _getter: (module: TModule) => TAction) { - } - } - `), - "/home/src/workspaces/project/global.d.ts": stringtestutil.Dedent(` - interface PromiseConstructor { - new (): Promise; - } - declare var Promise: PromiseConstructor; - interface Promise { - } - `), - "/home/src/workspaces/project/index.ts": stringtestutil.Dedent(` - import { LazyAction, LazyModule } from './bundling'; - const lazyModule = new LazyModule(() => - import('./lazyIndex') - ); - export const lazyBar = new LazyAction(lazyModule, m => m.bar); - `), - "/home/src/workspaces/project/lazyIndex.ts": stringtestutil.Dedent(` - export { default as bar } from './bar'; - `) + lazyExtraContents, - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(fmt.Sprintf(` - { - "compilerOptions": { - "target": "es5", - "declaration": true, - "outDir": "obj", - "incremental": true, - "isolatedModules": %t, - }, - }`, isolatedModules)), - } - } - testCases := []*tscInput{ - { - subScenario: "inferred type from transitive module", - files: getBuildInferredTypeFromTransitiveModuleMap(false, ""), - commandLineArgs: []string{"--b", "--verbose"}, - edits: []*tscEdit{ - { - caption: "incremental-declaration-changes", - edit: func(sys *testSys) { - sys.replaceFileText("/home/src/workspaces/project/bar.ts", "param: string", "") - }, - }, - { - caption: "incremental-declaration-changes", - edit: func(sys *testSys) { - sys.replaceFileText("/home/src/workspaces/project/bar.ts", "foobar()", "foobar(param: string)") - }, - }, - }, - }, - { - subScenario: "inferred type from transitive module with isolatedModules", - files: getBuildInferredTypeFromTransitiveModuleMap(true, ""), - commandLineArgs: []string{"--b", "--verbose"}, - edits: []*tscEdit{ - { - caption: "incremental-declaration-changes", - edit: func(sys *testSys) { - sys.replaceFileText("/home/src/workspaces/project/bar.ts", "param: string", "") - }, - }, - { - caption: "incremental-declaration-changes", - edit: func(sys *testSys) { - sys.replaceFileText("/home/src/workspaces/project/bar.ts", "foobar()", "foobar(param: string)") - }, - }, - }, - }, - { - subScenario: "reports errors in files affected by change in signature with isolatedModules", - files: getBuildInferredTypeFromTransitiveModuleMap(true, stringtestutil.Dedent(` - import { default as bar } from './bar'; - bar("hello"); - `)), - commandLineArgs: []string{"--b", "--verbose"}, - edits: []*tscEdit{ - { - caption: "incremental-declaration-changes", - edit: func(sys *testSys) { - sys.replaceFileText("/home/src/workspaces/project/bar.ts", "param: string", "") - }, - }, - { - caption: "incremental-declaration-changes", - edit: func(sys *testSys) { - sys.replaceFileText("/home/src/workspaces/project/bar.ts", "foobar()", "foobar(param: string)") - }, - }, - { - caption: "incremental-declaration-changes", - edit: func(sys *testSys) { - sys.replaceFileText("/home/src/workspaces/project/bar.ts", "param: string", "") - }, - }, - { - caption: "Fix Error", - edit: func(sys *testSys) { - sys.replaceFileText("/home/src/workspaces/project/lazyIndex.ts", `bar("hello")`, "bar()") - }, - }, - }, - }, - } - - for _, test := range testCases { - test.run(t, "inferredTypeFromTransitiveModule") - } -} - -func TestBuildJavascriptProjectEmit(t *testing.T) { - t.Parallel() - testCases := []*tscInput{ - { - // !!! sheetal errors seem different - subScenario: "loads js-based projects and emits them correctly", - files: FileMap{ - "/home/src/workspaces/solution/common/nominal.js": stringtestutil.Dedent(` - /** - * @template T, Name - * @typedef {T & {[Symbol.species]: Name}} Nominal - */ - module.exports = {}; - `), - "/home/src/workspaces/solution/common/tsconfig.json": stringtestutil.Dedent(` - { - "extends": "../tsconfig.base.json", - "compilerOptions": { - "composite": true, - }, - "include": ["nominal.js"], - } - `), - "/home/src/workspaces/solution/sub-project/index.js": stringtestutil.Dedent(` - import { Nominal } from '../common/nominal'; - - /** - * @typedef {Nominal} MyNominal - */ - `), - "/home/src/workspaces/solution/sub-project/tsconfig.json": stringtestutil.Dedent(` - { - "extends": "../tsconfig.base.json", - "compilerOptions": { - "composite": true, - }, - "references": [ - { "path": "../common" }, - ], - "include": ["./index.js"], - }`), - "/home/src/workspaces/solution/sub-project-2/index.js": stringtestutil.Dedent(` - import { MyNominal } from '../sub-project/index'; - - const variable = { - key: /** @type {MyNominal} */('value'), - }; - - /** - * @return {keyof typeof variable} - */ - export function getVar() { - return 'key'; - } - `), - "/home/src/workspaces/solution/sub-project-2/tsconfig.json": stringtestutil.Dedent(` - { - "extends": "../tsconfig.base.json", - "compilerOptions": { - "composite": true, - }, - "references": [ - { "path": "../sub-project" }, - ], - "include": ["./index.js"], - }`), - "/home/src/workspaces/solution/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - }, - "references": [ - { "path": "./sub-project" }, - { "path": "./sub-project-2" }, - ], - "include": [], - }`), - "/home/src/workspaces/solution/tsconfig.base.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "skipLibCheck": true, - "rootDir": "./", - "outDir": "../lib", - "allowJs": true, - "checkJs": true, - "declaration": true, - }, - }`), - tscLibPath + "/lib.d.ts": strings.Replace(tscDefaultLibContent, "interface SymbolConstructor {", "interface SymbolConstructor {\n readonly species: symbol;", 1), - }, - cwd: "/home/src/workspaces/solution", - commandLineArgs: []string{"--b"}, - }, - { - subScenario: `loads js-based projects with non-moved json files and emits them correctly`, - files: FileMap{ - "/home/src/workspaces/solution/common/obj.json": stringtestutil.Dedent(` - { - "val": 42, - }`), - "/home/src/workspaces/solution/common/index.ts": stringtestutil.Dedent(` - import x = require("./obj.json"); - export = x; - `), - "/home/src/workspaces/solution/common/tsconfig.json": stringtestutil.Dedent(` - { - "extends": "../tsconfig.base.json", - "compilerOptions": { - "outDir": null, - "composite": true, - }, - "include": ["index.ts", "obj.json"], - }`), - "/home/src/workspaces/solution/sub-project/index.js": stringtestutil.Dedent(` - import mod from '../common'; - - export const m = mod; - `), - "/home/src/workspaces/solution/sub-project/tsconfig.json": stringtestutil.Dedent(` - { - "extends": "../tsconfig.base.json", - "compilerOptions": { - "composite": true, - }, - "references": [ - { "path": "../common" }, - ], - "include": ["./index.js"], - }`), - "/home/src/workspaces/solution/sub-project-2/index.js": stringtestutil.Dedent(` - import { m } from '../sub-project/index'; - - const variable = { - key: m, - }; - - export function getVar() { - return variable; - } - `), - "/home/src/workspaces/solution/sub-project-2/tsconfig.json": stringtestutil.Dedent(` - { - "extends": "../tsconfig.base.json", - "compilerOptions": { - "composite": true, - }, - "references": [ - { "path": "../sub-project" }, - ], - "include": ["./index.js"], - }`), - "/home/src/workspaces/solution/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - }, - "references": [ - { "path": "./sub-project" }, - { "path": "./sub-project-2" }, - ], - "include": [], - }`), - "/home/src/workspaces/solution/tsconfig.base.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "skipLibCheck": true, - "rootDir": "./", - "outDir": "../out", - "allowJs": true, - "checkJs": true, - "resolveJsonModule": true, - "esModuleInterop": true, - "declaration": true, - }, - }`), - }, - cwd: "/home/src/workspaces/solution", - commandLineArgs: []string{"-b"}, - }, - } - - for _, test := range testCases { - test.run(t, "javascriptProjectEmit") - } -} - -func TestBuildLateBoundSymbol(t *testing.T) { - t.Parallel() - testCases := []*tscInput{ - { - subScenario: "interface is merged and contains late bound member", - files: FileMap{ - "/home/src/workspaces/project/src/globals.d.ts": stringtestutil.Dedent(` - interface SymbolConstructor { - (description?: string | number): symbol; - } - declare var Symbol: SymbolConstructor; - `), - "/home/src/workspaces/project/src/hkt.ts": `export interface HKT { }`, - "/home/src/workspaces/project/src/main.ts": stringtestutil.Dedent(` - import { HKT } from "./hkt"; - - const sym = Symbol(); - - declare module "./hkt" { - interface HKT { - [sym]: { a: T } - } - } - const x = 10; - type A = HKT[typeof sym]; - `), - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "rootDir": "src", - "incremental": true, - }, - }`), - }, - commandLineArgs: []string{"--b", "--verbose"}, - edits: []*tscEdit{ - { - caption: "incremental-declaration-doesnt-change", - edit: func(sys *testSys) { - sys.replaceFileText("/home/src/workspaces/project/src/main.ts", "const x = 10;", "") - }, - }, - { - caption: "incremental-declaration-doesnt-change", - edit: func(sys *testSys) { - sys.appendFile("/home/src/workspaces/project/src/main.ts", "const x = 10;") - }, - }, - }, - }, - } - - for _, test := range testCases { - test.run(t, "lateBoundSymbol") - } -} - -func TestBuildModuleSpecifiers(t *testing.T) { - t.Parallel() - testCases := []*tscInput{ - { - subScenario: `synthesized module specifiers resolve correctly`, - files: FileMap{ - "/home/src/workspaces/packages/solution/common/nominal.ts": stringtestutil.Dedent(` - export declare type Nominal = T & { - [Symbol.species]: Name; - }; - `), - "/home/src/workspaces/packages/solution/common/tsconfig.json": stringtestutil.Dedent(` - { - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "composite": true - }, - "include": ["nominal.ts"] - } - `), - "/home/src/workspaces/packages/solution/sub-project/index.ts": stringtestutil.Dedent(` - import { Nominal } from '../common/nominal'; - - export type MyNominal = Nominal; - `), - "/home/src/workspaces/packages/solution/sub-project/tsconfig.json": stringtestutil.Dedent(` - { - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "composite": true - }, - "references": [ - { "path": "../common" } - ], - "include": ["./index.ts"] - } - `), - "/home/src/workspaces/packages/solution/sub-project-2/index.ts": stringtestutil.Dedent(` - import { MyNominal } from '../sub-project/index'; - - const variable = { - key: 'value' as MyNominal, - }; - - export function getVar(): keyof typeof variable { - return 'key'; - } - `), - "/home/src/workspaces/packages/solution/sub-project-2/tsconfig.json": stringtestutil.Dedent(` - { - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "composite": true - }, - "references": [ - { "path": "../sub-project" } - ], - "include": ["./index.ts"] - } - `), - "/home/src/workspaces/packages/solution/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true - }, - "references": [ - { "path": "./sub-project" }, - { "path": "./sub-project-2" } - ], - "include": [] - } - `), - "/home/src/workspaces/packages/tsconfig.base.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "skipLibCheck": true, - "rootDir": "./", - "outDir": "lib" - } - } - `), - "/home/src/workspaces/packages/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true - }, - "references": [ - { "path": "./solution" }, - ], - "include": [], - } - `), - tscLibPath + "/lib.d.ts": strings.Replace(tscDefaultLibContent, "interface SymbolConstructor {", "interface SymbolConstructor {\n readonly species: symbol;", 1), - }, - cwd: "/home/src/workspaces/packages", - commandLineArgs: []string{"-b", "--verbose"}, - }, - { - subScenario: `synthesized module specifiers across projects resolve correctly`, - files: FileMap{ - "/home/src/workspaces/packages/src-types/index.ts": stringtestutil.Dedent(` - export * from './dogconfig.js';`), - "/home/src/workspaces/packages/src-types/dogconfig.ts": stringtestutil.Dedent(` - export interface DogConfig { - name: string; - } - `), - "/home/src/workspaces/packages/src-dogs/index.ts": stringtestutil.Dedent(` - export * from 'src-types'; - export * from './lassie/lassiedog.js'; - `), - "/home/src/workspaces/packages/src-dogs/dogconfig.ts": stringtestutil.Dedent(` - import { DogConfig } from 'src-types'; - - export const DOG_CONFIG: DogConfig = { - name: 'Default dog', - }; - `), - "/home/src/workspaces/packages/src-dogs/dog.ts": stringtestutil.Dedent(` - import { DogConfig } from 'src-types'; - import { DOG_CONFIG } from './dogconfig.js'; - - export abstract class Dog { - - public static getCapabilities(): DogConfig { - return DOG_CONFIG; - } - } - `), - "/home/src/workspaces/packages/src-dogs/lassie/lassiedog.ts": stringtestutil.Dedent(` - import { Dog } from '../dog.js'; - import { LASSIE_CONFIG } from './lassieconfig.js'; - - export class LassieDog extends Dog { - protected static getDogConfig = () => LASSIE_CONFIG; - } - `), - "/home/src/workspaces/packages/src-dogs/lassie/lassieconfig.ts": stringtestutil.Dedent(` - import { DogConfig } from 'src-types'; - - export const LASSIE_CONFIG: DogConfig = { name: 'Lassie' }; - `), - "/home/src/workspaces/packages/tsconfig-base.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "declaration": true, - "module": "node16", - }, - } - `), - "/home/src/workspaces/packages/src-types/package.json": stringtestutil.Dedent(` - { - "type": "module", - "exports": "./index.js" - }`), - "/home/src/workspaces/packages/src-dogs/package.json": stringtestutil.Dedent(` - { - "type": "module", - "exports": "./index.js" - }`), - "/home/src/workspaces/packages/src-types/tsconfig.json": stringtestutil.Dedent(` - { - "extends": "../tsconfig-base.json", - "compilerOptions": { - "composite": true, - }, - "include": [ - "**/*", - ], - }`), - "/home/src/workspaces/packages/src-dogs/tsconfig.json": stringtestutil.Dedent(` - { - "extends": "../tsconfig-base.json", - "compilerOptions": { - "composite": true, - }, - "references": [ - { "path": "../src-types" }, - ], - "include": [ - "**/*", - ], - }`), - "/home/src/workspaces/packages/src-types/node_modules": vfstest.Symlink("/home/src/workspaces/packages"), - "/home/src/workspaces/packages/src-dogs/node_modules": vfstest.Symlink("/home/src/workspaces/packages"), - }, - cwd: "/home/src/workspaces/packages", - commandLineArgs: []string{"-b", "src-types", "src-dogs", "--verbose"}, - }, - } - - for _, test := range testCases { - test.run(t, "moduleSpecifiers") - } -} - -func TestBuildOutputPaths(t *testing.T) { - t.Parallel() - type tscOutputPathScenario struct { - subScenario string - files FileMap - expectedDtsNames []string - } - runOutputPaths := func(s *tscOutputPathScenario) { - t.Helper() - input := &tscInput{ - subScenario: s.subScenario, - files: s.files, - commandLineArgs: []string{"-b", "-v"}, - edits: []*tscEdit{ - noChange, - { - caption: "Normal build without change, that does not block emit on error to show files that get emitted", - commandLineArgs: []string{"-p", "/home/src/workspaces/project/tsconfig.json"}, - }, - }, - } - input.run(t, "outputPaths") - t.Run("GetOutputFileNames/"+s.subScenario, func(t *testing.T) { - t.Parallel() - sys := newTestSys(input, false) - config, _ := tsoptions.GetParsedCommandLineOfConfigFile("/home/src/workspaces/project/tsconfig.json", &core.CompilerOptions{}, sys, nil) - assert.DeepEqual(t, slices.Collect(config.GetOutputFileNames()), s.expectedDtsNames) - }) - } - testCases := []*tscOutputPathScenario{ - { - subScenario: "when rootDir is not specified", - files: FileMap{ - "/home/src/workspaces/project/src/index.ts": "export const x = 10;", - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "outDir": "dist", - }, - }`), - }, - expectedDtsNames: []string{ - "/home/src/workspaces/project/dist/index.js", - }, - }, - { - subScenario: "when rootDir is not specified and is composite", - files: FileMap{ - "/home/src/workspaces/project/src/index.ts": "export const x = 10;", - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "outDir": "dist", - "composite": true, - }, - }`), - }, - expectedDtsNames: []string{ - "/home/src/workspaces/project/dist/src/index.js", - "/home/src/workspaces/project/dist/src/index.d.ts", - }, - }, - { - subScenario: "when rootDir is specified", - files: FileMap{ - "/home/src/workspaces/project/src/index.ts": "export const x = 10;", - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "outDir": "dist", - "rootDir": "src", - }, - }`), - }, - expectedDtsNames: []string{ - "/home/src/workspaces/project/dist/index.js", - }, - }, - { - // !!! sheetal error missing as not yet implemented - subScenario: "when rootDir is specified but not all files belong to rootDir", - files: FileMap{ - "/home/src/workspaces/project/src/index.ts": "export const x = 10;", - "/home/src/workspaces/project/types/type.ts": "export type t = string;", - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "outDir": "dist", - "rootDir": "src", - }, - }`), - }, - expectedDtsNames: []string{ - "/home/src/workspaces/project/dist/index.js", - "/home/src/workspaces/project/types/type.js", - }, - }, - { - // !!! sheetal error missing as not yet implemented - subScenario: "when rootDir is specified but not all files belong to rootDir and is composite", - files: FileMap{ - "/home/src/workspaces/project/src/index.ts": "export const x = 10;", - "/home/src/workspaces/project/types/type.ts": "export type t = string;", - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "outDir": "dist", - "rootDir": "src", - "composite": true - }, - }`), - }, - expectedDtsNames: []string{ - "/home/src/workspaces/project/dist/index.js", - "/home/src/workspaces/project/dist/index.d.ts", - "/home/src/workspaces/project/types/type.js", - "/home/src/workspaces/project/types/type.d.ts", - }, - }, - } - for _, test := range testCases { - runOutputPaths(test) - } -} - -func TestBuildProgramUpdates(t *testing.T) { - t.Parallel() - testCases := []*tscInput{ - { - subScenario: "when referenced project change introduces error in the down stream project and then fixes it", - files: FileMap{ - "/user/username/projects/sample1/Library/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true - } - }`), - "/user/username/projects/sample1/Library/library.ts": stringtestutil.Dedent(` - interface SomeObject - { - message: string; - } - - export function createSomeObject(): SomeObject - { - return { - message: "new Object" - }; - } - `), - "/user/username/projects/sample1/App/tsconfig.json": stringtestutil.Dedent(` - { - "references": [{ "path": "../Library" }] - }`), - "/user/username/projects/sample1/App/app.ts": stringtestutil.Dedent(` - import { createSomeObject } from "../Library/library"; - createSomeObject().message; - `), - }, - cwd: "/user/username/projects/sample1", - commandLineArgs: []string{"-b", "-w", "App"}, - edits: []*tscEdit{ - { - caption: "Introduce error", - // Change message in library to message2 - edit: func(sys *testSys) { - sys.replaceFileTextAll("/user/username/projects/sample1/Library/library.ts", "message", "message2") - }, - }, - { - caption: "Fix error", - // Revert library changes - edit: func(sys *testSys) { - sys.replaceFileTextAll("/user/username/projects/sample1/Library/library.ts", "message2", "message") - }, - }, - }, - }, - { - subScenario: "declarationEmitErrors when fixing error files all files are emitted", - files: FileMap{ - "/user/username/projects/solution/app/fileWithError.ts": stringtestutil.Dedent(` - export var myClassWithError = class { - tags() { } - private p = 12 - }; - `), - "/user/username/projects/solution/app/fileWithoutError.ts": "export class myClass { }", - "/user/username/projects/solution/app/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true - } - }`), - }, - cwd: "/user/username/projects/solution", - commandLineArgs: []string{"-b", "-w", "app"}, - edits: []*tscEdit{ - { - caption: "Fix error", - edit: func(sys *testSys) { - sys.replaceFileText("/user/username/projects/solution/app/fileWithError.ts", "private p = 12", "") - }, - }, - }, - }, - { - subScenario: "declarationEmitErrors when file with no error changes", - files: FileMap{ - "/user/username/projects/solution/app/fileWithError.ts": stringtestutil.Dedent(` - export var myClassWithError = class { - tags() { } - private p = 12 - }; - `), - "/user/username/projects/solution/app/fileWithoutError.ts": "export class myClass { }", - "/user/username/projects/solution/app/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true - } - }`), - }, - cwd: "/user/username/projects/solution", - commandLineArgs: []string{"-b", "-w", "app"}, - edits: []*tscEdit{ - { - caption: "Change fileWithoutError", - edit: func(sys *testSys) { - sys.replaceFileTextAll("/user/username/projects/solution/app/fileWithoutError.ts", "myClass", "myClass2") - }, - }, - }, - }, - { - subScenario: "declarationEmitErrors introduceError when fixing errors only changed file is emitted", - files: FileMap{ - "/user/username/projects/solution/app/fileWithError.ts": stringtestutil.Dedent(` - export var myClassWithError = class { - tags() { } - - }; - `), - "/user/username/projects/solution/app/fileWithoutError.ts": "export class myClass { }", - "/user/username/projects/solution/app/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true - } - }`), - }, - cwd: "/user/username/projects/solution", - commandLineArgs: []string{"-b", "-w", "app"}, - edits: []*tscEdit{ - { - caption: "Introduce error", - edit: func(sys *testSys) { - sys.writeFileNoError("/user/username/projects/solution/app/fileWithError.ts", stringtestutil.Dedent(` - export var myClassWithError = class { - tags() { } - private p = 12 - }; - `), false) - }, - }, - { - caption: "Fix error", - edit: func(sys *testSys) { - sys.replaceFileText("/user/username/projects/solution/app/fileWithError.ts", "private p = 12", "") - }, - }, - }, - }, - { - subScenario: "declarationEmitErrors introduceError when file with no error changes", - files: FileMap{ - "/user/username/projects/solution/app/fileWithError.ts": stringtestutil.Dedent(` - export var myClassWithError = class { - tags() { } - - }; - `), - "/user/username/projects/solution/app/fileWithoutError.ts": "export class myClass { }", - "/user/username/projects/solution/app/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true - } - }`), - }, - cwd: "/user/username/projects/solution", - commandLineArgs: []string{"-b", "-w", "app"}, - edits: []*tscEdit{ - { - caption: "Introduce error", - edit: func(sys *testSys) { - sys.writeFileNoError("/user/username/projects/solution/app/fileWithError.ts", stringtestutil.Dedent(` - export var myClassWithError = class { - tags() { } - private p = 12 - }; - `), false) - }, - }, - { - caption: "Change fileWithoutError", - edit: func(sys *testSys) { - sys.replaceFileTextAll("/user/username/projects/solution/app/fileWithoutError.ts", "myClass", "myClass2") - }, - }, - }, - }, - { - subScenario: "works when noUnusedParameters changes to false", - files: FileMap{ - "/user/username/projects/myproject/index.ts": `const fn = (a: string, b: string) => b;`, - "/user/username/projects/myproject/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "noUnusedParameters": true, - }, - }`), - }, - cwd: "/user/username/projects/myproject", - commandLineArgs: []string{"-b", "-w"}, - - edits: []*tscEdit{ - { - caption: "Change tsconfig to set noUnusedParameters to false", - edit: func(sys *testSys) { - sys.writeFileNoError( - `/user/username/projects/myproject/tsconfig.json`, - stringtestutil.Dedent(` - { - "compilerOptions": { - "noUnusedParameters": false, - }, - }`), - false, - ) - }, - }, - }, - }, - { - subScenario: "works with extended source files", - cwd: "/user/username/projects/project", - files: FileMap{ - "/user/username/projects/project/commonFile1.ts": "let x = 1", - "/user/username/projects/project/commonFile2.ts": "let y = 1", - "/user/username/projects/project/alpha.tsconfig.json": "{}", - "/user/username/projects/project/project1.tsconfig.json": stringtestutil.Dedent(` - { - "extends": "./alpha.tsconfig.json", - "compilerOptions": { - "composite": true, - }, - "files": ["commonFile1.ts", "commonFile2.ts"], - } - `), - "/user/username/projects/project/bravo.tsconfig.json": stringtestutil.Dedent(` - { - "extends": "./alpha.tsconfig.json", - } - `), - "/user/username/projects/project/other.ts": "let z = 0;", - "/user/username/projects/project/project2.tsconfig.json": stringtestutil.Dedent(` - { - "extends": "./bravo.tsconfig.json", - "compilerOptions": { - "composite": true, - }, - "files": ["other.ts"], - } - `), - "/user/username/projects/project/other2.ts": "let k = 0;", - "/user/username/projects/project/extendsConfig1.tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - }, - } - `), - "/user/username/projects/project/extendsConfig2.tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "strictNullChecks": false, - }, - } - `), - "/user/username/projects/project/extendsConfig3.tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "noImplicitAny": true, - }, - } - `), - "/user/username/projects/project/project3.tsconfig.json": stringtestutil.Dedent(` - { - "extends": [ - "./extendsConfig1.tsconfig.json", - "./extendsConfig2.tsconfig.json", - "./extendsConfig3.tsconfig.json", - ], - "compilerOptions": { - "composite": false, - }, - "files": ["other2.ts"], - }`), - }, - commandLineArgs: []string{"-b", "-w", "-v", "project1.tsconfig.json", "project2.tsconfig.json", "project3.tsconfig.json"}, - edits: []*tscEdit{ - { - caption: "Modify alpha config", - edit: func(sys *testSys) { - sys.writeFileNoError("/user/username/projects/project/alpha.tsconfig.json", stringtestutil.Dedent(` - { - "compilerOptions": { - "strict": true - } - }`), false) - }, - }, - { - caption: "change bravo config", - edit: func(sys *testSys) { - sys.writeFileNoError("/user/username/projects/project/bravo.tsconfig.json", stringtestutil.Dedent(` - { - "extends": "./alpha.tsconfig.json", - "compilerOptions": { "strict": false } - }`), false) - }, - }, - { - caption: "project 2 extends alpha", - edit: func(sys *testSys) { - sys.writeFileNoError("/user/username/projects/project/project2.tsconfig.json", stringtestutil.Dedent(` - { - "extends": "./alpha.tsconfig.json", - }`), false) - }, - }, - { - caption: "update aplha config", - edit: func(sys *testSys) { - sys.writeFileNoError("/user/username/projects/project/alpha.tsconfig.json", "{}", false) - }, - }, - { - caption: "Modify extendsConfigFile2", - edit: func(sys *testSys) { - sys.writeFileNoError("/user/username/projects/project/extendsConfig2.tsconfig.json", stringtestutil.Dedent(` - { - "compilerOptions": { "strictNullChecks": true } - }`), false) - }, - }, - { - caption: "Modify project 3", - edit: func(sys *testSys) { - sys.writeFileNoError("/user/username/projects/project/project3.tsconfig.json", stringtestutil.Dedent(` - { - "extends": ["./extendsConfig1.tsconfig.json", "./extendsConfig2.tsconfig.json"], - "compilerOptions": { "composite": false }, - "files": ["other2.ts"], - }`), false) - }, - }, - { - caption: "Delete extendedConfigFile2 and report error", - edit: func(sys *testSys) { - sys.removeNoError("/user/username/projects/project/extendsConfig2.tsconfig.json") - }, - }, - }, - }, - { - subScenario: "works correctly when project with extended config is removed", - files: FileMap{ - "/user/username/projects/project/commonFile1.ts": "let x = 1", - "/user/username/projects/project/commonFile2.ts": "let y = 1", - "/user/username/projects/project/alpha.tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "strict": true, - }, - }`), - "/user/username/projects/project/project1.tsconfig.json": stringtestutil.Dedent(` - { - "extends": "./alpha.tsconfig.json", - "compilerOptions": { - "composite": true, - }, - "files": ["commonFile1.ts", "commonFile2.ts"], - }`), - "/user/username/projects/project/bravo.tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "strict": true, - }, - }`), - "/user/username/projects/project/other.ts": "let z = 0;", - "/user/username/projects/project/project2.tsconfig.json": stringtestutil.Dedent(` - { - "extends": "./bravo.tsconfig.json", - "compilerOptions": { - "composite": true, - }, - "files": ["other.ts"], - }`), - "/user/username/projects/project/tsconfig.json": stringtestutil.Dedent(` - { - "references": [ - { - "path": "./project1.tsconfig.json", - }, - { - "path": "./project2.tsconfig.json", - }, - ], - "files": [], - }`), - }, - cwd: "/user/username/projects/project", - commandLineArgs: []string{"-b", "-w", "-v"}, - edits: []*tscEdit{ - { - caption: "Remove project2 from base config", - edit: func(sys *testSys) { - sys.writeFileNoError("/user/username/projects/project/tsconfig.json", stringtestutil.Dedent(` - { - "references": [ - { - "path": "./project1.tsconfig.json", - }, - ], - "files": [], - }`), false) - }, - }, - }, - }, - { - subScenario: "tsbuildinfo has error", - files: FileMap{ - "/user/username/projects/project/main.ts": "export const x = 10;", - "/user/username/projects/project/tsconfig.json": "{}", - "/user/username/projects/project/tsconfig.tsbuildinfo": "Some random string", - }, - cwd: "/user/username/projects/project", - commandLineArgs: []string{"--b", "-i", "-w"}, - }, - { - subScenario: "when root is source from project reference", - files: FileMap{ - "/home/src/workspaces/project/lib/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "outDir": "./dist" - } - }`), - "/home/src/workspaces/project/lib/foo.ts": `export const FOO: string = 'THEFOOEXPORT';`, - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "references": [ { "path": "./lib" } ] - }`), - "/home/src/workspaces/project/index.ts": `import { FOO } from "./lib/foo";`, - }, - commandLineArgs: []string{"--b"}, - edits: []*tscEdit{ - { - caption: "dts doesnt change", - edit: func(sys *testSys) { - sys.appendFile("/home/src/workspaces/project/lib/foo.ts", "const Bar = 10;") - }, - }, - }, - cwd: "/home/src/workspaces/project", - }, - { - subScenario: "when root is source from project reference with composite", - files: FileMap{ - "/home/src/workspaces/project/lib/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "outDir": "./dist" - } - }`), - "/home/src/workspaces/project/lib/foo.ts": `export const FOO: string = 'THEFOOEXPORT';`, - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - }, - "references": [ { "path": "./lib" } ] - }`), - "/home/src/workspaces/project/index.ts": `import { FOO } from "./lib/foo";`, - }, - commandLineArgs: []string{"--b"}, - edits: []*tscEdit{ - { - caption: "dts doesnt change", - edit: func(sys *testSys) { - sys.appendFile("/home/src/workspaces/project/lib/foo.ts", "const Bar = 10;") - }, - }, - }, - cwd: "/home/src/workspaces/project", - }, - } - for _, test := range testCases { - test.run(t, "programUpdates") - } -} - -func TestBuildProjectsBuilding(t *testing.T) { - t.Parallel() - addPackageFiles := func(files FileMap, index int) { - files[fmt.Sprintf(`/user/username/projects/myproject/pkg%d/index.ts`, index)] = fmt.Sprintf(`export const pkg%d = %d;`, index, index) - var references string - if index > 0 { - references = `"references": [{ "path": "../pkg0" }],` - } - files[fmt.Sprintf(`/user/username/projects/myproject/pkg%d/tsconfig.json`, index)] = stringtestutil.Dedent(fmt.Sprintf(` - { - "compilerOptions": { "composite": true }, - %s - }`, references)) - } - addSolution := func(files FileMap, count int) { - var pkgReferences []string - for i := range count { - pkgReferences = append(pkgReferences, fmt.Sprintf(`{ "path": "./pkg%d" }`, i)) - } - files[`/user/username/projects/myproject/tsconfig.json`] = stringtestutil.Dedent(fmt.Sprintf(` - { - "compilerOptions": { "composite": true }, - "references": [ - %s - ] - }`, strings.Join(pkgReferences, ",\n\t\t\t\t"))) - } - files := func(count int) FileMap { - files := FileMap{} - for i := range count { - addPackageFiles(files, i) - } - addSolution(files, count) - return files - } - - getTestCases := func(pkgCount int) []*tscInput { - edits := []*tscEdit{ - { - caption: "dts doesn't change", - edit: func(sys *testSys) { - sys.appendFile(`/user/username/projects/myproject/pkg0/index.ts`, `const someConst2 = 10;`) - }, - }, - noChange, - { - caption: "dts change", - edit: func(sys *testSys) { - sys.appendFile(`/user/username/projects/myproject/pkg0/index.ts`, `export const someConst = 10;`) - }, - }, - noChange, - } - return []*tscInput{ - { - subScenario: fmt.Sprintf(`when there are %d projects in a solution`, pkgCount), - files: files(pkgCount), - cwd: "/user/username/projects/myproject", - commandLineArgs: []string{"-b", "-v"}, - edits: edits, - }, - { - subScenario: fmt.Sprintf(`when there are %d projects in a solution`, pkgCount), - files: files(pkgCount), - cwd: "/user/username/projects/myproject", - commandLineArgs: []string{"-b", "-w", "-v"}, - edits: edits, - }, - } - } - - testCases := slices.Concat( - getTestCases(3), - getTestCases(5), - getTestCases(8), - getTestCases(23), - ) - - for _, test := range testCases { - test.run(t, "projectsBuilding") - } -} - -func TestBuildProjectReferenceWithRootDirInParent(t *testing.T) { - t.Parallel() - getBuildProjectReferenceWithRootDirInParentFileMap := func(modify func(files FileMap)) FileMap { - files := FileMap{ - "/home/src/workspaces/solution/src/main/a.ts": stringtestutil.Dedent(` - import { b } from './b'; - const a = b; - `), - "/home/src/workspaces/solution/src/main/b.ts": stringtestutil.Dedent(` - export const b = 0; - `), - "/home/src/workspaces/solution/src/main/tsconfig.json": stringtestutil.Dedent(` - { - "extends": "../../tsconfig.base.json", - "references": [ - { "path": "../other" }, - ], - }`), - "/home/src/workspaces/solution/src/other/other.ts": stringtestutil.Dedent(` - export const Other = 0; - `), - "/home/src/workspaces/solution/src/other/tsconfig.json": stringtestutil.Dedent(` - { - "extends": "../../tsconfig.base.json", - } - `), - "/home/src/workspaces/solution/tsconfig.base.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "declaration": true, - "rootDir": "./src/", - "outDir": "./dist/", - "skipDefaultLibCheck": true, - }, - "exclude": [ - "node_modules", - ], - }`), - } - if modify != nil { - modify(files) - } - return files - } - testCases := []*tscInput{ - { - subScenario: "builds correctly", - files: getBuildProjectReferenceWithRootDirInParentFileMap(nil), - cwd: "/home/src/workspaces/solution", - commandLineArgs: []string{"--b", "src/main", "/home/src/workspaces/solution/src/other"}, - }, - { - subScenario: "reports error for same tsbuildinfo file because no rootDir in the base", - files: getBuildProjectReferenceWithRootDirInParentFileMap( - func(files FileMap) { - text, _ := files["/home/src/workspaces/solution/tsconfig.base.json"] - files["/home/src/workspaces/solution/tsconfig.base.json"] = strings.Replace(text.(string), `"rootDir": "./src/",`, "", 1) - }, - ), - cwd: "/home/src/workspaces/solution", - commandLineArgs: []string{"--b", "src/main", "--verbose"}, - }, - { - subScenario: "reports error for same tsbuildinfo file", - files: getBuildProjectReferenceWithRootDirInParentFileMap( - func(files FileMap) { - files["/home/src/workspaces/solution/src/main/tsconfig.json"] = stringtestutil.Dedent(` - { - "compilerOptions": { "composite": true, "outDir": "../../dist/" }, - "references": [{ "path": "../other" }] - }`) - files["/home/src/workspaces/solution/src/other/tsconfig.json"] = stringtestutil.Dedent(` - { - "compilerOptions": { "composite": true, "outDir": "../../dist/" }, - }`) - }, - ), - cwd: "/home/src/workspaces/solution", - commandLineArgs: []string{"--b", "src/main", "--verbose"}, - edits: noChangeOnlyEdit, - }, - { - subScenario: "reports error for same tsbuildinfo file without incremental", - files: getBuildProjectReferenceWithRootDirInParentFileMap( - func(files FileMap) { - files["/home/src/workspaces/solution/src/main/tsconfig.json"] = stringtestutil.Dedent(` - { - "compilerOptions": { "outDir": "../../dist/" }, - "references": [{ "path": "../other" }] - }`) - files["/home/src/workspaces/solution/src/other/tsconfig.json"] = stringtestutil.Dedent(` - { - "compilerOptions": { "composite": true, "outDir": "../../dist/" }, - }`) - }, - ), - cwd: "/home/src/workspaces/solution", - commandLineArgs: []string{"--b", "src/main", "--verbose"}, - }, - { - subScenario: "reports error for same tsbuildinfo file without incremental with tsc", - files: getBuildProjectReferenceWithRootDirInParentFileMap( - func(files FileMap) { - files["/home/src/workspaces/solution/src/main/tsconfig.json"] = stringtestutil.Dedent(` - { - "compilerOptions": { "outDir": "../../dist/" }, - "references": [{ "path": "../other" }] - }`) - files["/home/src/workspaces/solution/src/other/tsconfig.json"] = stringtestutil.Dedent(` - { - "compilerOptions": { "composite": true, "outDir": "../../dist/" }, - }`) - }, - ), - cwd: "/home/src/workspaces/solution", - commandLineArgs: []string{"--b", "src/other", "--verbose"}, - edits: []*tscEdit{ - { - caption: "Running tsc on main", - commandLineArgs: []string{"-p", "src/main"}, - }, - }, - }, - { - subScenario: "reports no error when tsbuildinfo differ", - files: getBuildProjectReferenceWithRootDirInParentFileMap( - func(files FileMap) { - delete(files, "/home/src/workspaces/solution/src/main/tsconfig.json") - delete(files, "/home/src/workspaces/solution/src/other/tsconfig.json") - files["/home/src/workspaces/solution/src/main/tsconfig.main.json"] = stringtestutil.Dedent(` - { - "compilerOptions": { "composite": true, "outDir": "../../dist/" }, - "references": [{ "path": "../other/tsconfig.other.json" }] - }`) - files["/home/src/workspaces/solution/src/other/tsconfig.other.json"] = stringtestutil.Dedent(` - { - "compilerOptions": { "composite": true, "outDir": "../../dist/" }, - }`) - }, - ), - cwd: "/home/src/workspaces/solution", - commandLineArgs: []string{"--b", "src/main/tsconfig.main.json", "--verbose"}, - edits: noChangeOnlyEdit, - }, - } - - for _, test := range testCases { - test.run(t, "projectReferenceWithRootDirInParent") - } -} - -func TestBuildReexport(t *testing.T) { - t.Parallel() - testCases := []*tscInput{ - { - subScenario: "Reports errors correctly", - files: FileMap{ - "/user/username/projects/reexport/src/tsconfig.json": stringtestutil.Dedent(` - { - "files": [], - "include": [], - "references": [{ "path": "./pure" }, { "path": "./main" }], - }`), - "/user/username/projects/reexport/src/main/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "outDir": "../../out", - "rootDir": "../", - }, - "include": ["**/*.ts"], - "references": [{ "path": "../pure" }], - }`), - "/user/username/projects/reexport/src/main/index.ts": stringtestutil.Dedent(` - import { Session } from "../pure"; - - export const session: Session = { - foo: 1 - }; - `), - "/user/username/projects/reexport/src/pure/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "outDir": "../../out", - "rootDir": "../", - }, - "include": ["**/*.ts"], - }`), - "/user/username/projects/reexport/src/pure/index.ts": `export * from "./session";`, - "/user/username/projects/reexport/src/pure/session.ts": stringtestutil.Dedent(` - export interface Session { - foo: number; - // bar: number; - } - `), - }, - cwd: `/user/username/projects/reexport`, - commandLineArgs: []string{"-b", "-w", "-verbose", "src"}, - edits: []*tscEdit{ - { - caption: "Introduce error", - edit: func(sys *testSys) { - sys.replaceFileText(`/user/username/projects/reexport/src/pure/session.ts`, "// ", "") - }, - }, - { - caption: "Fix error", - edit: func(sys *testSys) { - sys.replaceFileText(`/user/username/projects/reexport/src/pure/session.ts`, "bar: ", "// bar: ") - }, - }, - }, - }, - } - - for _, test := range testCases { - test.run(t, "reexport") - } -} - -func TestBuildResolveJsonModule(t *testing.T) { - t.Parallel() - type buildResolveJsonModuleScenario struct { - subScenario string - tsconfigFiles string - additionalCompilerOptions string - skipOutdir bool - modifyFiles func(files FileMap) - edits []*tscEdit - } - getBuildResolveJsonModuleFileMap := func(composite bool, s *buildResolveJsonModuleScenario) FileMap { - var outDirStr string - if !s.skipOutdir { - outDirStr = `"outDir": "dist",` - } - files := FileMap{ - "/home/src/workspaces/solution/project/src/hello.json": stringtestutil.Dedent(` - { - "hello": "world" - }`), - "/home/src/workspaces/solution/project/src/index.ts": stringtestutil.Dedent(` - import hello from "./hello.json" - export default hello.hello - `), - "/home/src/workspaces/solution/project/tsconfig.json": stringtestutil.Dedent(fmt.Sprintf(` - { - "compilerOptions": { - "composite": %t, - "moduleResolution": "node", - "module": "commonjs", - "resolveJsonModule": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - %s - "skipDefaultLibCheck": true, - %s - }, - %s - }`, composite, outDirStr, s.additionalCompilerOptions, s.tsconfigFiles)), - } - if s.modifyFiles != nil { - s.modifyFiles(files) - } - return files - } - getBuildResolveJsonModuleTestCases := func(scenarios []*buildResolveJsonModuleScenario) []*tscInput { - testCases := make([]*tscInput, 0, len(scenarios)*2) - for _, s := range scenarios { - testCases = append( - testCases, - &tscInput{ - subScenario: s.subScenario, - files: getBuildResolveJsonModuleFileMap(true, s), - cwd: "/home/src/workspaces/solution", - commandLineArgs: []string{"--b", "project", "--v", "--explainFiles", "--listEmittedFiles"}, - edits: s.edits, - }, - &tscInput{ - subScenario: s.subScenario + " non-composite", - files: getBuildResolveJsonModuleFileMap(false, s), - cwd: "/home/src/workspaces/solution", - commandLineArgs: []string{"--b", "project", "--v", "--explainFiles", "--listEmittedFiles"}, - edits: s.edits, - }, - ) - } - return testCases - } - scenarios := []*buildResolveJsonModuleScenario{ - { - subScenario: "include only", - tsconfigFiles: `"include": [ "src/**/*" ],`, - }, - { - subScenario: "include only without outDir", - tsconfigFiles: `"include": [ "src/**/*" ],`, - skipOutdir: true, - }, - { - subScenario: "include only with json not in rootDir", - tsconfigFiles: `"include": [ "src/**/*" ],`, - additionalCompilerOptions: `"rootDir": "src",`, - modifyFiles: func(files FileMap) { - text, _ := files["/home/src/workspaces/solution/project/src/hello.json"] - delete(files, "/home/src/workspaces/solution/project/src/hello.json") - files["/home/src/workspaces/solution/project/hello.json"] = text - text, _ = files["/home/src/workspaces/solution/project/src/index.ts"] - files["/home/src/workspaces/solution/project/src/index.ts"] = strings.Replace(text.(string), "./hello.json", "../hello.json", 1) - }, - }, - { - subScenario: "include only with json without rootDir but outside configDirectory", - tsconfigFiles: `"include": [ "src/**/*" ],`, - modifyFiles: func(files FileMap) { - text, _ := files["/home/src/workspaces/solution/project/src/hello.json"] - delete(files, "/home/src/workspaces/solution/project/src/hello.json") - files["/home/src/workspaces/solution/hello.json"] = text - text, _ = files["/home/src/workspaces/solution/project/src/index.ts"] - files["/home/src/workspaces/solution/project/src/index.ts"] = strings.Replace(text.(string), "./hello.json", "../../hello.json", 1) - }, - }, - { - subScenario: "include of json along with other include", - tsconfigFiles: `"include": [ "src/**/*", "src/**/*.json" ],`, - }, - { - subScenario: "include of json along with other include and file name matches ts file", - tsconfigFiles: `"include": [ "src/**/*", "src/**/*.json" ],`, - modifyFiles: func(files FileMap) { - text, _ := files["/home/src/workspaces/solution/project/src/hello.json"] - delete(files, "/home/src/workspaces/solution/project/src/hello.json") - files["/home/src/workspaces/solution/project/src/index.json"] = text - text, _ = files["/home/src/workspaces/solution/project/src/index.ts"] - files["/home/src/workspaces/solution/project/src/index.ts"] = strings.Replace(text.(string), "./hello.json", "./index.json", 1) - }, - }, - { - subScenario: "files containing json file", - tsconfigFiles: `"files": [ "src/index.ts", "src/hello.json", ],`, - }, - { - subScenario: "include and files", - tsconfigFiles: `"files": [ "src/hello.json" ], "include": [ "src/**/*" ],`, - }, - { - subScenario: "sourcemap", - tsconfigFiles: `"files": [ "src/index.ts", "src/hello.json", ],`, - additionalCompilerOptions: `"sourceMap": true,`, - edits: noChangeOnlyEdit, - }, - { - subScenario: "without outDir", - tsconfigFiles: `"files": [ "src/index.ts", "src/hello.json", ],`, - skipOutdir: true, - edits: noChangeOnlyEdit, - }, - } - testCases := slices.Concat( - getBuildResolveJsonModuleTestCases(scenarios), - []*tscInput{ - { - subScenario: "importing json module from project reference", - files: FileMap{ - "/home/src/workspaces/solution/project/strings/foo.json": stringtestutil.Dedent(` - { - "foo": "bar baz" - } - `), - "/home/src/workspaces/solution/project/strings/tsconfig.json": stringtestutil.Dedent(` - { - "extends": "../tsconfig.json", - "include": ["foo.json"], - "references": [], - } - `), - "/home/src/workspaces/solution/project/main/index.ts": stringtestutil.Dedent(` - import { foo } from '../strings/foo.json'; - console.log(foo); - `), - "/home/src/workspaces/solution/project/main/tsconfig.json": stringtestutil.Dedent(` - { - "extends": "../tsconfig.json", - "include": [ - "./**/*.ts", - ], - "references": [{ - "path": "../strings/tsconfig.json", - }], - } - `), - "/home/src/workspaces/solution/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "target": "es5", - "module": "commonjs", - "rootDir": "./", - "composite": true, - "resolveJsonModule": true, - "strict": true, - "esModuleInterop": true, - }, - "references": [ - { "path": "./strings/tsconfig.json" }, - { "path": "./main/tsconfig.json" }, - ], - "files": [], - } - `), - }, - cwd: "/home/src/workspaces/solution", - commandLineArgs: []string{"--b", "project", "--verbose", "--explainFiles"}, - edits: noChangeOnlyEdit, - }, - }, - ) - - for _, test := range testCases { - test.run(t, "resolveJsonModule") - } -} - -func TestBuildRoots(t *testing.T) { - t.Parallel() - getBuildRootsFromProjectReferencedProjectFileMap := func(serverFirst bool) FileMap { - include := core.IfElse(serverFirst, `"src/**/*.ts", "../shared/src/**/*.ts"`, `"../shared/src/**/*.ts", "src/**/*.ts"`) - return FileMap{ - "/home/src/workspaces/solution/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - }, - "references": [ - { "path": "projects/server" }, - { "path": "projects/shared" }, - ], - }`), - "/home/src/workspaces/solution/projects/shared/src/myClass.ts": `export class MyClass { }`, - "/home/src/workspaces/solution/projects/shared/src/logging.ts": stringtestutil.Dedent(` - export function log(str: string) { - console.log(str); - } - `), - "/home/src/workspaces/solution/projects/shared/src/random.ts": stringtestutil.Dedent(` - export function randomFn(str: string) { - console.log(str); - } - `), - "/home/src/workspaces/solution/projects/shared/tsconfig.json": stringtestutil.Dedent(` - { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "./dist", - }, - "include": ["src/**/*.ts"], - }`), - "/home/src/workspaces/solution/projects/server/src/server.ts": stringtestutil.Dedent(` - import { MyClass } from ':shared/myClass.js'; - console.log('Hello, world!'); - `), - "/home/src/workspaces/solution/projects/server/tsconfig.json": stringtestutil.Dedent(fmt.Sprintf(` - { - "extends": "../../tsconfig.json", - "compilerOptions": { - "rootDir": "..", - "outDir": "./dist", - "paths": { - ":shared/*": ["./src/../../shared/src/*"], - }, - }, - "include": [ %s ], - "references": [ - { "path": "../shared" }, - ], - }`, include)), - } - } - getBuildRootsFromProjectReferencedProjectTestEdits := func() []*tscEdit { - return []*tscEdit{ - noChange, - { - caption: "edit logging file", - edit: func(sys *testSys) { - sys.appendFile("/home/src/workspaces/solution/projects/shared/src/logging.ts", "export const x = 10;") - }, - }, - noChange, - { - caption: "delete random file", - edit: func(sys *testSys) { - sys.removeNoError("/home/src/workspaces/solution/projects/shared/src/random.ts") - }, - }, - noChange, - } - } - testCases := []*tscInput{ - { - subScenario: `when two root files are consecutive`, - files: FileMap{ - "/home/src/workspaces/project/file1.ts": `export const x = "hello";`, - "/home/src/workspaces/project/file2.ts": `export const y = "world";`, - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { "composite": true }, - "include": ["*.ts"], - }`), - }, - commandLineArgs: []string{"--b", "-v"}, - edits: []*tscEdit{ - { - caption: "delete file1", - edit: func(sys *testSys) { - sys.removeNoError("/home/src/workspaces/project/file1.ts") - sys.removeNoError("/home/src/workspaces/project/file1.js") - sys.removeNoError("/home/src/workspaces/project/file1.d.ts") - }, - }, - }, - }, - { - subScenario: `when multiple root files are consecutive`, - files: FileMap{ - "/home/src/workspaces/project/file1.ts": `export const x = "hello";`, - "/home/src/workspaces/project/file2.ts": `export const y = "world";`, - "/home/src/workspaces/project/file3.ts": `export const y = "world";`, - "/home/src/workspaces/project/file4.ts": `export const y = "world";`, - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { "composite": true }, - "include": ["*.ts"], - }`), - }, - commandLineArgs: []string{"--b", "-v"}, - edits: []*tscEdit{ - { - caption: "delete file1", - edit: func(sys *testSys) { - sys.removeNoError("/home/src/workspaces/project/file1.ts") - sys.removeNoError("/home/src/workspaces/project/file1.js") - sys.removeNoError("/home/src/workspaces/project/file1.d.ts") - }, - }, - }, - }, - { - subScenario: `when files are not consecutive`, - files: FileMap{ - "/home/src/workspaces/project/file1.ts": `export const x = "hello";`, - "/home/src/workspaces/project/random.d.ts": `export const random = "world";`, - "/home/src/workspaces/project/file2.ts": stringtestutil.Dedent(` - import { random } from "./random"; - export const y = "world"; - `), - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { "composite": true }, - "include": ["file*.ts"], - }`), - }, - commandLineArgs: []string{"--b", "-v"}, - edits: []*tscEdit{ - { - caption: "delete file1", - edit: func(sys *testSys) { - sys.removeNoError("/home/src/workspaces/project/file1.ts") - sys.removeNoError("/home/src/workspaces/project/file1.js") - sys.removeNoError("/home/src/workspaces/project/file1.d.ts") - }, - }, - }, - }, - { - subScenario: `when consecutive and non consecutive are mixed`, - files: FileMap{ - "/home/src/workspaces/project/file1.ts": `export const x = "hello";`, - "/home/src/workspaces/project/file2.ts": `export const y = "world";`, - "/home/src/workspaces/project/random.d.ts": `export const random = "hello";`, - "/home/src/workspaces/project/nonconsecutive.ts": stringtestutil.Dedent(` - import { random } from "./random"; - export const nonConsecutive = "hello"; - `), - "/home/src/workspaces/project/random1.d.ts": `export const random = "hello";`, - "/home/src/workspaces/project/asArray1.ts": stringtestutil.Dedent(` - import { random } from "./random1"; - export const x = "hello"; - `), - "/home/src/workspaces/project/asArray2.ts": `export const x = "hello";`, - "/home/src/workspaces/project/asArray3.ts": `export const x = "hello";`, - "/home/src/workspaces/project/random2.d.ts": `export const random = "hello";`, - "/home/src/workspaces/project/anotherNonConsecutive.ts": stringtestutil.Dedent(` - import { random } from "./random2"; - export const nonConsecutive = "hello"; - `), - "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { "composite": true }, - "include": ["file*.ts", "nonconsecutive*.ts", "asArray*.ts", "anotherNonConsecutive.ts"], - }`), - }, - commandLineArgs: []string{"--b", "-v"}, - edits: []*tscEdit{ - { - caption: "delete file1", - edit: func(sys *testSys) { - sys.removeNoError("/home/src/workspaces/project/file1.ts") - sys.removeNoError("/home/src/workspaces/project/file1.js") - sys.removeNoError("/home/src/workspaces/project/file1.d.ts") - }, - }, - }, - }, - { - subScenario: "when root file is from referenced project", - files: getBuildRootsFromProjectReferencedProjectFileMap(true), - cwd: "/home/src/workspaces/solution", - commandLineArgs: []string{"--b", "projects/server", "-v", "--traceResolution", "--explainFiles"}, - edits: getBuildRootsFromProjectReferencedProjectTestEdits(), - }, - { - subScenario: "when root file is from referenced project and shared is first", - files: getBuildRootsFromProjectReferencedProjectFileMap(false), - cwd: "/home/src/workspaces/solution", - commandLineArgs: []string{"--b", "projects/server", "-v", "--traceResolution", "--explainFiles"}, - edits: getBuildRootsFromProjectReferencedProjectTestEdits(), - }, - { - subScenario: "when root file is from referenced project", - files: getBuildRootsFromProjectReferencedProjectFileMap(true), - cwd: "/home/src/workspaces/solution", - commandLineArgs: []string{"--b", "-w", "projects/server", "-v", "--traceResolution", "--explainFiles"}, - edits: getBuildRootsFromProjectReferencedProjectTestEdits(), - }, - { - subScenario: "when root file is from referenced project and shared is first", - files: getBuildRootsFromProjectReferencedProjectFileMap(false), - cwd: "/home/src/workspaces/solution", - commandLineArgs: []string{"--b", "-w", "projects/server", "-v", "--traceResolution", "--explainFiles"}, - edits: getBuildRootsFromProjectReferencedProjectTestEdits(), - }, - } - - for _, test := range testCases { - test.run(t, "roots") - } -} - -func TestBuildSample(t *testing.T) { - t.Parallel() - - getLogicConfig := func() string { - return stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "declaration": true, - "sourceMap": true, - "skipDefaultLibCheck": true, - }, - "references": [ - { "path": "../core" }, - ], - }`) - } - - getBuildSampleFileMap := func(modify func(files FileMap)) FileMap { - files := FileMap{ - "/user/username/projects/sample1/core/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "declaration": true, - "declarationMap": true, - "skipDefaultLibCheck": true, - }, - }`), - "/user/username/projects/sample1/core/index.ts": stringtestutil.Dedent(` - export const someString: string = "HELLO WORLD"; - export function leftPad(s: string, n: number) { return s + n; } - export function multiply(a: number, b: number) { return a * b; } - `), - "/user/username/projects/sample1/core/some_decl.d.ts": `declare const dts: any;`, - "/user/username/projects/sample1/core/anotherModule.ts": `export const World = "hello";`, - "/user/username/projects/sample1/logic/tsconfig.json": getLogicConfig(), - "/user/username/projects/sample1/logic/index.ts": stringtestutil.Dedent(` - import * as c from '../core/index'; - export function getSecondsInDay() { - return c.multiply(10, 15); - } - import * as mod from '../core/anotherModule'; - export const m = mod; - `), - "/user/username/projects/sample1/tests/tsconfig.json": stringtestutil.Dedent(` - { - "references": [ - { "path": "../core" }, - { "path": "../logic" }, - ], - "files": ["index.ts"], - "compilerOptions": { - "composite": true, - "declaration": true, - "skipDefaultLibCheck": true, - }, - }`), - "/user/username/projects/sample1/tests/index.ts": stringtestutil.Dedent(` - import * as c from '../core/index'; - import * as logic from '../logic/index'; - - c.leftPad("", 10); - logic.getSecondsInDay(); - - import * as mod from '../core/anotherModule'; - export const m = mod; - `), - } - if modify != nil { - modify(files) - } - return files - } - getStopBuildOnErrorTests := func(options []string) []*tscInput { - noChange := core.IfElse(options == nil, noChangeOnlyEdit, nil) - return []*tscInput{ - { - subScenario: "skips builds downstream projects if upstream projects have errors with stopBuildOnErrors", - files: getBuildSampleFileMap(func(files FileMap) { - text, _ := files["/user/username/projects/sample1/core/index.ts"] - files["/user/username/projects/sample1/core/index.ts"] = text.(string) + `multiply();` - }), - cwd: "/user/username/projects/sample1", - commandLineArgs: slices.Concat([]string{"--b", "tests", "--verbose", "--stopBuildOnErrors"}, options), - edits: slices.Concat( - noChange, - []*tscEdit{ - { - caption: "fix error", - edit: func(sys *testSys) { - sys.replaceFileText("/user/username/projects/sample1/core/index.ts", "multiply();", "") - }, - }, - }, - ), - }, - { - subScenario: "skips builds downstream projects if upstream projects have errors with stopBuildOnErrors when test does not reference core", - files: getBuildSampleFileMap(func(files FileMap) { - files["/user/username/projects/sample1/tests/tsconfig.json"] = stringtestutil.Dedent(` - { - "references": [ - { "path": "../logic" }, - ], - "files": ["index.ts"], - "compilerOptions": { - "composite": true, - "declaration": true, - "skipDefaultLibCheck": true, - }, - }`) - text, _ := files["/user/username/projects/sample1/core/index.ts"] - files["/user/username/projects/sample1/core/index.ts"] = text.(string) + `multiply();` - }), - cwd: "/user/username/projects/sample1", - commandLineArgs: slices.Concat([]string{"--b", "tests", "--verbose", "--stopBuildOnErrors"}, options), - edits: slices.Concat( - noChange, - []*tscEdit{ - { - caption: "fix error", - edit: func(sys *testSys) { - sys.replaceFileText("/user/username/projects/sample1/core/index.ts", "multiply();", "") - }, - }, - }, - ), - }, - } - } - getBuildSampleCoreChangeEdits := func() []*tscEdit { - return []*tscEdit{ - { - caption: "incremental-declaration-changes", - edit: func(sys *testSys) { - sys.appendFile( - "/user/username/projects/sample1/core/index.ts", - ` -export class someClass { }`, - ) - }, - }, - { - caption: "incremental-declaration-doesnt-change", - edit: func(sys *testSys) { - sys.appendFile( - "/user/username/projects/sample1/core/index.ts", - ` -class someClass2 { }`, - ) - }, - }, - noChange, - } - } - getBuildSampleWatchDtsChangingEdits := func() []*tscEdit { - return []*tscEdit{ - { - caption: "Make change to core", - edit: func(sys *testSys) { - sys.appendFile("/user/username/projects/sample1/core/index.ts", "\nexport class someClass { }") - }, - }, - { - caption: "Revert core file", - edit: func(sys *testSys) { - sys.replaceFileText("/user/username/projects/sample1/core/index.ts", "\nexport class someClass { }", "") - }, - }, - { - caption: "Make two changes", - edit: func(sys *testSys) { - sys.appendFile("/user/username/projects/sample1/core/index.ts", "\nexport class someClass { }") - sys.appendFile("/user/username/projects/sample1/core/index.ts", "\nexport class someClass2 { }") - }, - }, - } - } - getBuildSampleWatchNonDtsChangingEdits := func() []*tscEdit { - return []*tscEdit{ - { - caption: "Make local change to core", - edit: func(sys *testSys) { - sys.appendFile("/user/username/projects/sample1/core/index.ts", "\nfunction foo() { }") - }, - }, - } - } - getBuildSampleWatchNewFileEdits := func() []*tscEdit { - return []*tscEdit{ - { - caption: "Change to new File and build core", - edit: func(sys *testSys) { - sys.writeFileNoError("/user/username/projects/sample1/core/newfile.ts", `export const newFileConst = 30;`, false) - }, - }, - { - caption: "Change to new File and build core", - edit: func(sys *testSys) { - sys.writeFileNoError("/user/username/projects/sample1/core/newfile.ts", "\nexport class someClass2 { }", false) - }, - }, - } - } - makeCircularReferences := func(files FileMap) { - files["/user/username/projects/sample1/core/tsconfig.json"] = stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "declaration": true - }, - "references": [ - { "path": "../tests", "circular": true } - ], - }`) - } - getIncrementalErrorTest := func(subScenario string, options []string) *tscInput { - var expectedDiffWithLogicError string - if slices.Contains(options, "--stopBuildOnErrors") { - expectedDiffWithLogicError = stringtestutil.Dedent(` - Clean build will stop on error in core and will not report error in logic - Watch build will retain previous errors from logic and report it - `) - } - return &tscInput{ - subScenario: "reportErrors " + subScenario, - files: getBuildSampleFileMap(nil), - cwd: "/user/username/projects/sample1", - commandLineArgs: slices.Concat([]string{"-b", "-w", "tests"}, options), - edits: []*tscEdit{ - { - caption: "change logic", - edit: func(sys *testSys) { - sys.appendFile("/user/username/projects/sample1/logic/index.ts", "\nlet y: string = 10;") - }, - }, - { - caption: "change core", - edit: func(sys *testSys) { - sys.appendFile("/user/username/projects/sample1/core/index.ts", "\nlet x: string = 10;") - }, - expectedDiff: expectedDiffWithLogicError, - }, - { - caption: "fix error in logic", - edit: func(sys *testSys) { - sys.replaceFileText("/user/username/projects/sample1/logic/index.ts", "\nlet y: string = 10;", "") - }, - }, - }, - } - } - testCases := slices.Concat([]*tscInput{ - { - subScenario: "builds correctly when outDir is specified", - files: getBuildSampleFileMap(func(files FileMap) { - files["/user/username/projects/sample1/logic/tsconfig.json"] = stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "declaration": true, - "sourceMap": true, - "outDir": "outDir", - }, - "references": [ - { "path": "../core" }, - ], - }`) - }), - cwd: "/user/username/projects/sample1", - commandLineArgs: []string{"--b", "tests"}, - }, - { - subScenario: "builds correctly when declarationDir is specified", - files: getBuildSampleFileMap(func(files FileMap) { - files["/user/username/projects/sample1/logic/tsconfig.json"] = stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "declaration": true, - "sourceMap": true, - "declarationDir": "out/decls", - }, - "references": [ - { "path": "../core" }, - ], - }`) - }), - cwd: "/user/username/projects/sample1", - commandLineArgs: []string{"--b", "tests"}, - }, - { - subScenario: "builds correctly when project is not composite or doesnt have any references", - files: getBuildSampleFileMap(func(files FileMap) { - text, _ := files["/user/username/projects/sample1/core/tsconfig.json"] - files["/user/username/projects/sample1/core/tsconfig.json"] = strings.Replace(text.(string), `"composite": true,`, "", 1) - }), - cwd: "/user/username/projects/sample1", - commandLineArgs: []string{"--b", "core", "--verbose"}, - }, - { - subScenario: "does not write any files in a dry build", - files: getBuildSampleFileMap(nil), - cwd: "/user/username/projects/sample1", - commandLineArgs: []string{"--b", "tests", "--dry"}, - }, - { - subScenario: "removes all files it built", - files: getBuildSampleFileMap(nil), - cwd: "/user/username/projects/sample1", - commandLineArgs: []string{"--b", "tests"}, - edits: []*tscEdit{ - { - caption: "removes all files it built", - commandLineArgs: []string{"--b", "tests", "--clean"}, - }, - { - caption: "no change --clean", - commandLineArgs: []string{"--b", "tests", "--clean"}, - }, - }, - }, - { - subScenario: "cleaning project in not build order doesnt throw error", - files: getBuildSampleFileMap(nil), - cwd: "/user/username/projects/sample1", - commandLineArgs: []string{"--b", "logic2", "--clean"}, - }, - { - subScenario: "always builds under with force option", - files: getBuildSampleFileMap(nil), - cwd: "/user/username/projects/sample1", - commandLineArgs: []string{"--b", "tests", "--force"}, - edits: noChangeOnlyEdit, - }, - { - subScenario: "can detect when and what to rebuild", - files: getBuildSampleFileMap(nil), - cwd: "/user/username/projects/sample1", - commandLineArgs: []string{"--b", "tests", "--verbose"}, - edits: []*tscEdit{ - noChange, - { - // Update a file in the leaf node (tests), only it should rebuild the last one - caption: "Only builds the leaf node project", - edit: func(sys *testSys) { - sys.writeFileNoError("/user/username/projects/sample1/tests/index.ts", "const m = 10;", false) - }, - }, - { - // Update a file in the parent (without affecting types), should get fast downstream builds - caption: "Detects type-only changes in upstream projects", - edit: func(sys *testSys) { - sys.replaceFileText("/user/username/projects/sample1/core/index.ts", "HELLO WORLD", "WELCOME PLANET") - }, - }, - { - caption: "rebuilds when tsconfig changes", - edit: func(sys *testSys) { - sys.replaceFileText("/user/username/projects/sample1/tests/tsconfig.json", `"composite": true`, `"composite": true, "target": "es2020"`) - }, - }, - }, - }, - { - subScenario: "when input file text does not change but its modified time changes", - files: getBuildSampleFileMap(nil), - cwd: "/user/username/projects/sample1", - commandLineArgs: []string{"--b", "tests", "--verbose"}, - edits: []*tscEdit{ - { - caption: "upstream project changes without changing file text", - edit: func(sys *testSys) { - err := sys.FS().Chtimes("/user/username/projects/sample1/core/index.ts", time.Time{}, sys.Now()) - if err != nil { - panic(err) - } - }, - }, - }, - }, - { - subScenario: "when declarationMap changes", - files: getBuildSampleFileMap(nil), - cwd: "/user/username/projects/sample1", - commandLineArgs: []string{"--b", "tests", "--verbose"}, - edits: []*tscEdit{ - { - caption: "Disable declarationMap", - edit: func(sys *testSys) { - sys.replaceFileText("/user/username/projects/sample1/core/tsconfig.json", `"declarationMap": true,`, `"declarationMap": false,`) - }, - }, - { - caption: "Enable declarationMap", - edit: func(sys *testSys) { - sys.replaceFileText("/user/username/projects/sample1/core/tsconfig.json", `"declarationMap": false,`, `"declarationMap": true,`) - }, - }, - }, - }, - { - subScenario: "indicates that it would skip builds during a dry build", - files: getBuildSampleFileMap(nil), - cwd: "/user/username/projects/sample1", - commandLineArgs: []string{"--b", "tests"}, - edits: []*tscEdit{ - { - caption: "--dry", - commandLineArgs: []string{"--b", "tests", "--dry"}, - }, - }, - }, - { - subScenario: "rebuilds from start if force option is set", - files: getBuildSampleFileMap(nil), - cwd: "/user/username/projects/sample1", - commandLineArgs: []string{"--b", "tests"}, - edits: []*tscEdit{ - { - caption: "--force build", - commandLineArgs: []string{"--b", "tests", "--verbose", "--force"}, - }, - }, - }, - { - subScenario: "tsbuildinfo has error", - files: FileMap{ - "/home/src/workspaces/project/main.ts": "export const x = 10;", - "/home/src/workspaces/project/tsconfig.json": "{}", - "/home/src/workspaces/project/tsconfig.tsbuildinfo": "Some random string", - }, - commandLineArgs: []string{"--b", "-i", "-v"}, - edits: []*tscEdit{ - { - caption: "tsbuildinfo written has error", - edit: func(sys *testSys) { - // This is to ensure the non incremental doesnt crash - as it wont have tsbuildInfo - if !sys.forIncrementalCorrectness { - sys.prependFile("/home/src/workspaces/project/tsconfig.tsbuildinfo", "Some random string") - sys.replaceFileText("/home/src/workspaces/project/tsconfig.tsbuildinfo", fmt.Sprintf(`"version":"%s"`, core.Version()), fmt.Sprintf(`"version":"%s"`, harnessutil.FakeTSVersion)) // build info won't parse, need to manually sterilize for baseline - } - }, - }, - }, - }, - { - subScenario: "rebuilds completely when version in tsbuildinfo doesnt match ts version", - files: getBuildSampleFileMap(nil), - cwd: "/user/username/projects/sample1", - commandLineArgs: []string{"--b", "tests", "--verbose"}, - edits: []*tscEdit{ - { - caption: "convert tsbuildInfo version to something that is say to previous version", - edit: func(sys *testSys) { - // This is to ensure the non incremental doesnt crash - as it wont have tsbuildInfo - if !sys.forIncrementalCorrectness { - sys.replaceFileText("/user/username/projects/sample1/core/tsconfig.tsbuildinfo", fmt.Sprintf(`"version":"%s"`, harnessutil.FakeTSVersion), fmt.Sprintf(`"version":"%s"`, "FakeTsPreviousVersion")) - sys.replaceFileText("/user/username/projects/sample1/logic/tsconfig.tsbuildinfo", fmt.Sprintf(`"version":"%s"`, harnessutil.FakeTSVersion), fmt.Sprintf(`"version":"%s"`, "FakeTsPreviousVersion")) - sys.replaceFileText("/user/username/projects/sample1/tests/tsconfig.tsbuildinfo", fmt.Sprintf(`"version":"%s"`, harnessutil.FakeTSVersion), fmt.Sprintf(`"version":"%s"`, "FakeTsPreviousVersion")) - } - }, - }, - }, - }, - { - subScenario: "rebuilds when extended config file changes", - files: getBuildSampleFileMap(func(files FileMap) { - files["/user/username/projects/sample1/tests/tsconfig.base.json"] = stringtestutil.Dedent(` - { - "compilerOptions": { - "target": "es5" - } - }`) - text, _ := files["/user/username/projects/sample1/tests/tsconfig.json"] - files["/user/username/projects/sample1/tests/tsconfig.json"] = strings.Replace(text.(string), `"references": [`, `"extends": "./tsconfig.base.json", "references": [`, 1) - }), - cwd: "/user/username/projects/sample1", - commandLineArgs: []string{"--b", "tests", "--verbose"}, - edits: []*tscEdit{ - { - caption: "change extended file", - edit: func(sys *testSys) { - sys.writeFileNoError("/user/username/projects/sample1/tests/tsconfig.base.json", stringtestutil.Dedent(` - { - "compilerOptions": { } - }`), false) - }, - }, - }, - }, - { - subScenario: "building project in not build order doesnt throw error", - files: getBuildSampleFileMap(nil), - cwd: "/user/username/projects/sample1", - commandLineArgs: []string{"--b", "logic2/tsconfig.json", "--verbose"}, - }, - { - subScenario: "builds downstream projects even if upstream projects have errors", - files: getBuildSampleFileMap(func(files FileMap) { - text, _ := files["/user/username/projects/sample1/logic/index.ts"] - files["/user/username/projects/sample1/logic/index.ts"] = strings.Replace(text.(string), "c.multiply(10, 15)", `c.muitply()`, 1) - }), - cwd: "/user/username/projects/sample1", - commandLineArgs: []string{"--b", "tests", "--verbose"}, - edits: noChangeOnlyEdit, - }, - { - subScenario: "listFiles", - files: getBuildSampleFileMap(nil), - cwd: "/user/username/projects/sample1", - commandLineArgs: []string{"--b", "tests", "--listFiles"}, - edits: getBuildSampleCoreChangeEdits(), - }, - { - subScenario: "listEmittedFiles", - files: getBuildSampleFileMap(nil), - cwd: "/user/username/projects/sample1", - commandLineArgs: []string{"--b", "tests", "--listEmittedFiles"}, - edits: getBuildSampleCoreChangeEdits(), - }, - { - subScenario: "explainFiles", - files: getBuildSampleFileMap(nil), - cwd: "/user/username/projects/sample1", - commandLineArgs: []string{"--b", "tests", "--explainFiles", "--v"}, - edits: getBuildSampleCoreChangeEdits(), - }, - { - subScenario: "sample", - files: getBuildSampleFileMap(nil), - cwd: "/user/username/projects/sample1", - commandLineArgs: []string{"--b", "tests", "--verbose"}, - edits: slices.Concat( - getBuildSampleCoreChangeEdits(), - []*tscEdit{ - { - caption: "when logic config changes declaration dir", - edit: func(sys *testSys) { - sys.replaceFileText( - "/user/username/projects/sample1/logic/tsconfig.json", - `"declaration": true,`, - `"declaration": true, - "declarationDir": "decls",`, - ) - }, - }, - noChange, - }, - ), - }, - { - subScenario: "when logic specifies tsBuildInfoFile", - files: getBuildSampleFileMap(func(files FileMap) { - text, _ := files["/user/username/projects/sample1/logic/tsconfig.json"] - files["/user/username/projects/sample1/logic/tsconfig.json"] = strings.Replace( - text.(string), - `"composite": true,`, - `"composite": true, - "tsBuildInfoFile": "ownFile.tsbuildinfo",`, - 1, - ) - }), - cwd: "/user/username/projects/sample1", - commandLineArgs: []string{"--b", "tests", "--verbose"}, - }, - { - subScenario: "when declaration option changes", - files: getBuildSampleFileMap(func(files FileMap) { - files["/user/username/projects/sample1/core/tsconfig.json"] = stringtestutil.Dedent(` - { - "compilerOptions": { - "incremental": true, - "skipDefaultLibCheck": true, - }, - }`) - }), - cwd: "/user/username/projects/sample1", - commandLineArgs: []string{"--b", "core", "--verbose"}, - edits: []*tscEdit{ - { - caption: "incremental-declaration-changes", - edit: func(sys *testSys) { - sys.replaceFileText("/user/username/projects/sample1/core/tsconfig.json", `"incremental": true,`, `"incremental": true, "declaration": true,`) - }, - }, - }, - }, - { - subScenario: "when target option changes", - files: getBuildSampleFileMap(func(files FileMap) { - files[getTestLibPathFor("esnext.full")] = `/// -/// ` - files[tscLibPath+"/lib.d.ts"] = `/// -/// ` - files["/user/username/projects/sample1/core/tsconfig.json"] = stringtestutil.Dedent(` - { - "compilerOptions": { - "incremental": true, - "listFiles": true, - "listEmittedFiles": true, - "target": "esnext", - }, - }`) - }), - cwd: "/user/username/projects/sample1", - commandLineArgs: []string{"--b", "core", "--verbose"}, - edits: []*tscEdit{ - { - caption: "incremental-declaration-changes", - edit: func(sys *testSys) { - sys.replaceFileText("/user/username/projects/sample1/core/tsconfig.json", `esnext`, `es5`) - }, - }, - }, - }, - { - subScenario: "when module option changes", - files: getBuildSampleFileMap(func(files FileMap) { - files["/user/username/projects/sample1/core/tsconfig.json"] = stringtestutil.Dedent(` - { - "compilerOptions": { - "incremental": true, - "module": "node18", - }, - }`) - }), - cwd: "/user/username/projects/sample1", - commandLineArgs: []string{"--b", "core", "--verbose"}, - edits: []*tscEdit{ - { - caption: "incremental-declaration-changes", - edit: func(sys *testSys) { - sys.replaceFileText("/user/username/projects/sample1/core/tsconfig.json", `node18`, `nodenext`) - }, - }, - }, - }, - { - subScenario: "when esModuleInterop option changes", - files: getBuildSampleFileMap(func(files FileMap) { - files["/user/username/projects/sample1/tests/tsconfig.json"] = stringtestutil.Dedent(` - { - "references": [ - { "path": "../core" }, - { "path": "../logic" }, - ], - "files": ["index.ts"], - "compilerOptions": { - "composite": true, - "declaration": true, - "skipDefaultLibCheck": true, - "esModuleInterop": false, - }, - }`) - }), - cwd: "/user/username/projects/sample1", - commandLineArgs: []string{"--b", "tests", "--verbose"}, - edits: []*tscEdit{ - { - caption: "incremental-declaration-changes", - edit: func(sys *testSys) { - sys.replaceFileText("/user/username/projects/sample1/tests/tsconfig.json", `"esModuleInterop": false`, `"esModuleInterop": true`) - }, - }, - }, - }, - { - // !!! sheetal this is not reporting error as file not found is not yet implemented - subScenario: "reports error if input file is missing", - files: getBuildSampleFileMap(func(files FileMap) { - files["/user/username/projects/sample1/core/tsconfig.json"] = stringtestutil.Dedent(` - { - "compilerOptions": { "composite": true }, - "files": ["anotherModule.ts", "index.ts", "some_decl.d.ts"], - }`) - delete(files, "/user/username/projects/sample1/core/anotherModule.ts") - }), - cwd: "/user/username/projects/sample1", - commandLineArgs: []string{"--b", "tests", "--verbose"}, - }, - { - // !!! sheetal this is not reporting error as file not found is not yet implemented - subScenario: "reports error if input file is missing with force", - files: getBuildSampleFileMap(func(files FileMap) { - files["/user/username/projects/sample1/core/tsconfig.json"] = stringtestutil.Dedent(` - { - "compilerOptions": { "composite": true }, - "files": ["anotherModule.ts", "index.ts", "some_decl.d.ts"], - }`) - delete(files, "/user/username/projects/sample1/core/anotherModule.ts") - }), - cwd: "/user/username/projects/sample1", - commandLineArgs: []string{"--b", "tests", "--verbose", "--force"}, - }, - { - subScenario: "change builds changes and reports found errors message", - files: getBuildSampleFileMap(nil), - cwd: "/user/username/projects/sample1", - commandLineArgs: []string{"--b", "-w", "tests"}, - edits: getBuildSampleWatchDtsChangingEdits(), - }, - { - subScenario: "non local change does not start build of referencing projects", - files: getBuildSampleFileMap(nil), - cwd: "/user/username/projects/sample1", - commandLineArgs: []string{"--b", "-w", "tests"}, - edits: getBuildSampleWatchNonDtsChangingEdits(), - }, - { - subScenario: "builds when new file is added, and its subsequent updates", - files: getBuildSampleFileMap(nil), - cwd: "/user/username/projects/sample1", - commandLineArgs: []string{"--b", "-w", "tests"}, - edits: getBuildSampleWatchNewFileEdits(), - }, - { - subScenario: "change builds changes and reports found errors message with circular references", - files: getBuildSampleFileMap(makeCircularReferences), - cwd: "/user/username/projects/sample1", - commandLineArgs: []string{"--b", "-w", "tests"}, - edits: getBuildSampleWatchDtsChangingEdits(), - }, - { - subScenario: "non local change does not start build of referencing projects with circular references", - files: getBuildSampleFileMap(makeCircularReferences), - cwd: "/user/username/projects/sample1", - commandLineArgs: []string{"--b", "-w", "tests"}, - edits: getBuildSampleWatchNonDtsChangingEdits(), - }, - { - subScenario: "builds when new file is added, and its subsequent updates with circular references", - files: getBuildSampleFileMap(makeCircularReferences), - cwd: "/user/username/projects/sample1", - commandLineArgs: []string{"--b", "-w", "tests"}, - edits: getBuildSampleWatchNewFileEdits(), - }, - { - subScenario: "watches config files that are not present", - files: getBuildSampleFileMap(func(files FileMap) { - delete(files, "/user/username/projects/sample1/logic/tsconfig.json") - }), - cwd: "/user/username/projects/sample1", - commandLineArgs: []string{"--b", "-w", "tests"}, - edits: []*tscEdit{ - { - caption: "Write logic", - edit: func(sys *testSys) { - sys.writeFileNoError("/user/username/projects/sample1/logic/tsconfig.json", getLogicConfig(), false) - }, - }, - }, - }, - getIncrementalErrorTest("when preserveWatchOutput is not used", nil), - getIncrementalErrorTest("when preserveWatchOutput is passed on command line", []string{"--preserveWatchOutput"}), - getIncrementalErrorTest("when stopBuildOnErrors is passed on command line", []string{"--stopBuildOnErrors"}), - { - subScenario: "incremental updates in verbose mode", - files: getBuildSampleFileMap(nil), - cwd: "/user/username/projects/sample1", - commandLineArgs: []string{"--b", "-w", "tests", "--verbose"}, - edits: []*tscEdit{ - { - caption: "Make non dts change", - edit: func(sys *testSys) { - sys.appendFile("/user/username/projects/sample1/logic/index.ts", "\nfunction someFn() { }") - }, - }, - { - caption: "Make dts change", - edit: func(sys *testSys) { - sys.replaceFileText("/user/username/projects/sample1/logic/index.ts", "\nfunction someFn() { }", "\nexport function someFn() { }") - }, - }, - }, - }, - { - subScenario: "should not trigger recompilation because of program emit", - files: getBuildSampleFileMap(nil), - cwd: "/user/username/projects/sample1", - commandLineArgs: []string{"--b", "-w", "core", "--verbose"}, - edits: []*tscEdit{ - noChange, - { - caption: "Add new file", - edit: func(sys *testSys) { - sys.writeFileNoError("/user/username/projects/sample1/core/file3.ts", `export const y = 10;`, false) - }, - }, - noChange, - }, - }, - { - subScenario: "should not trigger recompilation because of program emit with outDir specified", - files: getBuildSampleFileMap(func(files FileMap) { - files["/user/username/projects/sample1/core/tsconfig.json"] = stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "outDir": "outDir" - } - }`) - }), - cwd: "/user/username/projects/sample1", - commandLineArgs: []string{"--b", "-w", "core", "--verbose"}, - edits: []*tscEdit{ - noChange, - { - caption: "Add new file", - edit: func(sys *testSys) { - sys.writeFileNoError("/user/username/projects/sample1/core/file3.ts", `export const y = 10;`, false) - }, - }, - noChange, - }, - }, - }, getStopBuildOnErrorTests(nil), getStopBuildOnErrorTests([]string{"--watch"})) - - for _, test := range testCases { - test.run(t, "sample") - } -} - -func TestBuildTransitiveReferences(t *testing.T) { - t.Parallel() - - getBuildTransitiveReferencesFileMap := func(modify func(files FileMap)) FileMap { - files := FileMap{ - "/user/username/projects/transitiveReferences/refs/a.d.ts": stringtestutil.Dedent(` - export class X {} - export class A {} - `), - "/user/username/projects/transitiveReferences/a.ts": stringtestutil.Dedent(` - export class A {} - `), - "/user/username/projects/transitiveReferences/b.ts": stringtestutil.Dedent(` - import {A} from '@ref/a'; - export const b = new A(); - `), - "/user/username/projects/transitiveReferences/c.ts": stringtestutil.Dedent(` - import {b} from './b'; - import {X} from "@ref/a"; - b; - X; - `), - "/user/username/projects/transitiveReferences/tsconfig.a.json": stringtestutil.Dedent(` - { - "files": ["a.ts"], - "compilerOptions": { - "composite": true, - }, - }`), - "/user/username/projects/transitiveReferences/tsconfig.b.json": stringtestutil.Dedent(` - { - "files": ["b.ts"], - "compilerOptions": { - "composite": true, - "paths": { - "@ref/*": ["./*"], - }, - }, - "references": [{ "path": "tsconfig.a.json" }], - }`), - "/user/username/projects/transitiveReferences/tsconfig.c.json": stringtestutil.Dedent(` - { - "files": ["c.ts"], - "compilerOptions": { - "paths": { - "@ref/*": ["./refs/*"], - }, - }, - "references": [{ "path": "tsconfig.b.json" }], - }`), - } - if modify != nil { - modify(files) - } - return files - } - testCases := []*tscInput{ - { - subScenario: "builds correctly", - files: getBuildTransitiveReferencesFileMap(nil), - cwd: "/user/username/projects/transitiveReferences", - commandLineArgs: []string{"--b", "tsconfig.c.json", "--listFiles"}, - }, - { - subScenario: "reports error about module not found with node resolution with external module name", - files: getBuildTransitiveReferencesFileMap(func(files FileMap) { - files["/user/username/projects/transitiveReferences/b.ts"] = `import {A} from 'a'; -export const b = new A();` - files["/user/username/projects/transitiveReferences/tsconfig.b.json"] = stringtestutil.Dedent(` - { - "files": ["b.ts"], - "compilerOptions": { - "composite": true, - "module": "nodenext", - }, - "references": [{ "path": "tsconfig.a.json" }], - }`) - }), - cwd: "/user/username/projects/transitiveReferences", - commandLineArgs: []string{"--b", "tsconfig.c.json", "--listFiles"}, - }, - } - - for _, test := range testCases { - test.run(t, "transitiveReferences") - } -} - -func TestBuildSolutionProject(t *testing.T) { - t.Parallel() - testCases := []*tscInput{ - { - subScenario: "verify that subsequent builds after initial build doesnt build anything", - files: FileMap{ - "/home/src/workspaces/solution/src/folder/index.ts": `export const x = 10;`, - "/home/src/workspaces/solution/src/folder/tsconfig.json": stringtestutil.Dedent(` - { - "files": ["index.ts"], - "compilerOptions": { - "composite": true - } - } - `), - "/home/src/workspaces/solution/src/folder2/index.ts": `export const x = 10;`, - "/home/src/workspaces/solution/src/folder2/tsconfig.json": stringtestutil.Dedent(` - { - "files": ["index.ts"], - "compilerOptions": { - "composite": true - } - } - `), - "/home/src/workspaces/solution/src/tsconfig.json": stringtestutil.Dedent(` - { - "files": [], - "compilerOptions": { - "composite": true - }, - "references": [ - { "path": "./folder" }, - { "path": "./folder2" }, - ] - }`), - "/home/src/workspaces/solution/tests/index.ts": `export const x = 10;`, - "/home/src/workspaces/solution/tests/tsconfig.json": stringtestutil.Dedent(` - { - "files": ["index.ts"], - "compilerOptions": { - "composite": true - }, - "references": [ - { "path": "../src" } - ] - } - `), - "/home/src/workspaces/solution/tsconfig.json": stringtestutil.Dedent(` - { - "files": [], - "compilerOptions": { - "composite": true - }, - "references": [ - { "path": "./src" }, - { "path": "./tests" } - ] - } - `), - }, - cwd: "/home/src/workspaces/solution", - commandLineArgs: []string{"--b", "--v"}, - edits: noChangeOnlyEdit, - }, - { - subScenario: "when solution is referenced indirectly", - files: FileMap{ - "/home/src/workspaces/solution/project1/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { "composite": true }, - "references": [] - } - `), - "/home/src/workspaces/solution/project2/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { "composite": true }, - "references": [] - } - `), - "/home/src/workspaces/solution/project2/src/b.ts": "export const b = 10;", - "/home/src/workspaces/solution/project3/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { "composite": true }, - "references": [ - { "path": "../project1" }, - { "path": "../project2" } - ] - } - `), - "/home/src/workspaces/solution/project3/src/c.ts": "export const c = 10;", - "/home/src/workspaces/solution/project4/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { "composite": true }, - "references": [{ "path": "../project3" }] - } - `), - "/home/src/workspaces/solution/project4/src/d.ts": "export const d = 10;", - }, - cwd: "/home/src/workspaces/solution", - commandLineArgs: []string{"--b", "project4", "--verbose", "--explainFiles"}, - edits: []*tscEdit{ - { - caption: "modify project3 file", - edit: func(sys *testSys) { - sys.replaceFileText("/home/src/workspaces/solution/project3/src/c.ts", "c = ", "cc = ") - }, - }, - }, - }, - { - subScenario: "has empty files diagnostic when files is empty and no references are provided", - files: FileMap{ - "/home/src/workspaces/solution/no-references/tsconfig.json": stringtestutil.Dedent(` - { - "references": [], - "files": [], - "compilerOptions": { - "composite": true, - "declaration": true, - "forceConsistentCasingInFileNames": true, - "skipDefaultLibCheck": true, - }, - }`), - }, - cwd: "/home/src/workspaces/solution", - commandLineArgs: []string{"--b", "no-references"}, - }, - { - subScenario: "does not have empty files diagnostic when files is empty and references are provided", - files: FileMap{ - "/home/src/workspaces/solution/core/index.ts": "export function multiply(a: number, b: number) { return a * b; }", - "/home/src/workspaces/solution/core/tsconfig.json": stringtestutil.Dedent(` - { - "compilerOptions": { - "composite": true, - "declaration": true, - "declarationMap": true, - "skipDefaultLibCheck": true, - }, - }`), - "/home/src/workspaces/solution/with-references/tsconfig.json": stringtestutil.Dedent(` - { - "references": [ - { "path": "../core" }, - ], - "files": [], - "compilerOptions": { - "composite": true, - "declaration": true, - "forceConsistentCasingInFileNames": true, - "skipDefaultLibCheck": true, - }, - }`), - }, - cwd: "/home/src/workspaces/solution", - commandLineArgs: []string{"--b", "with-references"}, - }, - } - - for _, test := range testCases { - test.run(t, "solution") - } -} diff --git a/kitcom/internal/tsgo/execute/tsctests/tscwatch_test.go b/kitcom/internal/tsgo/execute/tsctests/tscwatch_test.go deleted file mode 100644 index 58517bd..0000000 --- a/kitcom/internal/tsgo/execute/tsctests/tscwatch_test.go +++ /dev/null @@ -1,131 +0,0 @@ -package tsctests - -import ( - "strings" - "testing" -) - -func TestWatch(t *testing.T) { - t.Parallel() - testCases := []*tscInput{ - { - subScenario: "watch with no tsconfig", - files: FileMap{ - "/home/src/workspaces/project/index.ts": "", - }, - commandLineArgs: []string{"index.ts", "--watch"}, - }, - { - subScenario: "watch with tsconfig and incremental", - files: FileMap{ - "/home/src/workspaces/project/index.ts": "", - "/home/src/workspaces/project/tsconfig.json": "{}", - }, - commandLineArgs: []string{"--watch", "--incremental"}, - }, - } - - for _, test := range testCases { - test.run(t, "commandLineWatch") - } -} - -func listToTsconfig(base string, tsconfigOpts ...string) (string, string) { - optionString := strings.Join(tsconfigOpts, ",\n ") - tsconfigText := `{ - "compilerOptions": { -` - after := " " - if base != "" { - tsconfigText += " " + base - after = ",\n " - } - if len(tsconfigOpts) != 0 { - tsconfigText += after + optionString - } - tsconfigText += ` - } -}` - return tsconfigText, optionString -} - -func toTsconfig(base string, compilerOpts string) string { - tsconfigText, _ := listToTsconfig(base, compilerOpts) - return tsconfigText -} - -func noEmitWatchTestInput( - subScenario string, - commandLineArgs []string, - aText string, - tsconfigOptions []string, -) *tscInput { - noEmitOpt := `"noEmit": true` - tsconfigText, optionString := listToTsconfig(noEmitOpt, tsconfigOptions...) - return &tscInput{ - subScenario: subScenario, - commandLineArgs: commandLineArgs, - files: FileMap{ - "/home/src/workspaces/project/a.ts": aText, - "/home/src/workspaces/project/tsconfig.json": tsconfigText, - }, - edits: []*tscEdit{ - newTscEdit("fix error", func(sys *testSys) { - sys.writeFileNoError("/home/src/workspaces/project/a.ts", `const a = "hello";`, false) - }), - newTscEdit("emit after fixing error", func(sys *testSys) { - sys.writeFileNoError("/home/src/workspaces/project/tsconfig.json", toTsconfig("", optionString), false) - }), - newTscEdit("no emit run after fixing error", func(sys *testSys) { - sys.writeFileNoError("/home/src/workspaces/project/tsconfig.json", toTsconfig(noEmitOpt, optionString), false) - }), - newTscEdit("introduce error", func(sys *testSys) { - sys.writeFileNoError("/home/src/workspaces/project/a.ts", aText, false) - }), - newTscEdit("emit when error", func(sys *testSys) { - sys.writeFileNoError("/home/src/workspaces/project/tsconfig.json", toTsconfig("", optionString), false) - }), - newTscEdit("no emit run when error", func(sys *testSys) { - sys.writeFileNoError("/home/src/workspaces/project/tsconfig.json", toTsconfig(noEmitOpt, optionString), false) - }), - }, - } -} - -func newTscEdit(name string, edit func(sys *testSys)) *tscEdit { - return &tscEdit{name, []string{}, edit, ""} -} - -func TestTscNoEmitWatch(t *testing.T) { - t.Parallel() - - testCases := []*tscInput{ - noEmitWatchTestInput("syntax errors", - []string{"-w"}, - `const a = "hello`, - nil, - ), - noEmitWatchTestInput( - "semantic errors", - []string{"-w"}, - `const a: number = "hello"`, - nil, - ), - noEmitWatchTestInput( - "dts errors without dts enabled", - []string{"-w"}, - `const a = class { private p = 10; };`, - nil, - ), - noEmitWatchTestInput( - "dts errors", - []string{"-w"}, - `const a = class { private p = 10; };`, - []string{`"declaration": true`}, - ), - } - - for _, test := range testCases { - test.run(t, "noEmit") - } -} diff --git a/kitcom/internal/tsgo/execute/watcher.go b/kitcom/internal/tsgo/execute/watcher.go deleted file mode 100644 index 335bb1f..0000000 --- a/kitcom/internal/tsgo/execute/watcher.go +++ /dev/null @@ -1,160 +0,0 @@ -package execute - -import ( - "fmt" - "reflect" - "time" - - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/compiler" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/core" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/execute/incremental" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/execute/tsc" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tsoptions" -) - -type Watcher struct { - sys tsc.System - configFileName string - config *tsoptions.ParsedCommandLine - reportDiagnostic tsc.DiagnosticReporter - reportErrorSummary tsc.DiagnosticsReporter - testing tsc.CommandLineTesting - - host compiler.CompilerHost - program *incremental.Program - prevModified map[string]time.Time - configModified bool -} - -var _ tsc.Watcher = (*Watcher)(nil) - -func createWatcher(sys tsc.System, configParseResult *tsoptions.ParsedCommandLine, reportDiagnostic tsc.DiagnosticReporter, reportErrorSummary tsc.DiagnosticsReporter, testing tsc.CommandLineTesting) *Watcher { - w := &Watcher{ - sys: sys, - config: configParseResult, - reportDiagnostic: reportDiagnostic, - reportErrorSummary: reportErrorSummary, - testing: testing, - // reportWatchStatus: createWatchStatusReporter(sys, configParseResult.CompilerOptions().Pretty), - } - if configParseResult.ConfigFile != nil { - w.configFileName = configParseResult.ConfigFile.SourceFile.FileName() - } - return w -} - -func (w *Watcher) start() { - w.host = compiler.NewCompilerHost(w.sys.GetCurrentDirectory(), w.sys.FS(), w.sys.DefaultLibraryPath(), nil, getTraceFromSys(w.sys, w.testing)) - w.program = incremental.ReadBuildInfoProgram(w.config, incremental.NewBuildInfoReader(w.host), w.host) - - if w.testing == nil { - watchInterval := w.config.ParsedConfig.WatchOptions.WatchInterval() - for { - w.DoCycle() - time.Sleep(watchInterval) - } - } else { - // Initial compilation in test mode - w.DoCycle() - } -} - -func (w *Watcher) DoCycle() { - // if this function is updated, make sure to update `RunWatchCycle` in export_test.go as needed - - if w.hasErrorsInTsConfig() { - // these are unrecoverable errors--report them and do not build - return - } - // updateProgram() - w.program = incremental.NewProgram(compiler.NewProgram(compiler.ProgramOptions{ - Config: w.config, - Host: w.host, - JSDocParsingMode: ast.JSDocParsingModeParseForTypeErrors, - }), w.program, nil, w.testing != nil) - - if w.hasBeenModified(w.program.GetProgram()) { - fmt.Fprintln(w.sys.Writer(), "build starting at", w.sys.Now().Format("03:04:05 PM")) - timeStart := w.sys.Now() - w.compileAndEmit() - fmt.Fprintf(w.sys.Writer(), "build finished in %.3fs\n", w.sys.Now().Sub(timeStart).Seconds()) - } else { - // print something??? - // fmt.Fprintln(w.sys.Writer(), "no changes detected at ", w.sys.Now()) - } - if w.testing != nil { - w.testing.OnProgram(w.program) - } -} - -func (w *Watcher) compileAndEmit() { - // !!! output/error reporting is currently the same as non-watch mode - // diagnostics, emitResult, exitStatus := - tsc.EmitFilesAndReportErrors(tsc.EmitInput{ - Sys: w.sys, - ProgramLike: w.program, - Program: w.program.GetProgram(), - ReportDiagnostic: w.reportDiagnostic, - ReportErrorSummary: w.reportErrorSummary, - Writer: w.sys.Writer(), - CompileTimes: &tsc.CompileTimes{}, - Testing: w.testing, - }) -} - -func (w *Watcher) hasErrorsInTsConfig() bool { - // only need to check and reparse tsconfig options/update host if we are watching a config file - extendedConfigCache := &tsc.ExtendedConfigCache{} - if w.configFileName != "" { - // !!! need to check that this merges compileroptions correctly. This differs from non-watch, since we allow overriding of previous options - configParseResult, errors := tsoptions.GetParsedCommandLineOfConfigFile(w.configFileName, &core.CompilerOptions{}, w.sys, extendedConfigCache) - if len(errors) > 0 { - for _, e := range errors { - w.reportDiagnostic(e) - } - return true - } - // CompilerOptions contain fields which should not be compared; clone to get a copy without those set. - if !reflect.DeepEqual(w.config.CompilerOptions().Clone(), configParseResult.CompilerOptions().Clone()) { - // fmt.Fprintln(w.sys.Writer(), "build triggered due to config change") - w.configModified = true - } - w.config = configParseResult - } - w.host = compiler.NewCompilerHost(w.sys.GetCurrentDirectory(), w.sys.FS(), w.sys.DefaultLibraryPath(), extendedConfigCache, getTraceFromSys(w.sys, w.testing)) - return false -} - -func (w *Watcher) hasBeenModified(program *compiler.Program) bool { - // checks watcher's snapshot against program file modified times - currState := map[string]time.Time{} - filesModified := w.configModified - for _, sourceFile := range program.SourceFiles() { - fileName := sourceFile.FileName() - s := w.sys.FS().Stat(fileName) - if s == nil { - // do nothing; if file is in program.SourceFiles() but is not found when calling Stat, file has been very recently deleted. - // deleted files are handled outside of this loop - continue - } - currState[fileName] = s.ModTime() - if !filesModified { - if currState[fileName] != w.prevModified[fileName] { - // fmt.Fprint(w.sys.Writer(), "build triggered from ", fileName, ": ", w.prevModified[fileName], " -> ", currState[fileName], "\n") - filesModified = true - } - // catch cases where no files are modified, but some were deleted - delete(w.prevModified, fileName) - } - } - if !filesModified && len(w.prevModified) > 0 { - // fmt.Fprintln(w.sys.Writer(), "build triggered due to deleted file") - filesModified = true - } - w.prevModified = currState - - // reset state for next cycle - w.configModified = false - return filesModified -} diff --git a/kitcom/internal/tsgo/pprof/pprof.go b/kitcom/internal/tsgo/pprof/pprof.go deleted file mode 100644 index 23692f4..0000000 --- a/kitcom/internal/tsgo/pprof/pprof.go +++ /dev/null @@ -1,63 +0,0 @@ -package pprof - -import ( - "fmt" - "io" - "os" - "path/filepath" - "runtime/pprof" -) - -type profileSession struct { - cpuFilePath string - memFilePath string - cpuFile *os.File - memFile *os.File - logWriter io.Writer -} - -// BeginProfiling starts CPU and memory profiling, writing the profiles to the specified directory. -func BeginProfiling(profileDir string, logWriter io.Writer) *profileSession { - if err := os.MkdirAll(profileDir, 0o755); err != nil { - panic(err) - } - - pid := os.Getpid() - - cpuProfilePath := filepath.Join(profileDir, fmt.Sprintf("%d-cpuprofile.pb.gz", pid)) - memProfilePath := filepath.Join(profileDir, fmt.Sprintf("%d-memprofile.pb.gz", pid)) - cpuFile, err := os.Create(cpuProfilePath) - if err != nil { - panic(err) - } - memFile, err := os.Create(memProfilePath) - if err != nil { - panic(err) - } - - if err := pprof.StartCPUProfile(cpuFile); err != nil { - panic(err) - } - - return &profileSession{ - cpuFilePath: cpuProfilePath, - memFilePath: memProfilePath, - cpuFile: cpuFile, - memFile: memFile, - logWriter: logWriter, - } -} - -func (p *profileSession) Stop() { - pprof.StopCPUProfile() - err := pprof.Lookup("allocs").WriteTo(p.memFile, 0) - if err != nil { - panic(err) - } - - p.cpuFile.Close() - p.memFile.Close() - - fmt.Fprintf(p.logWriter, "CPU profile: %v\n", p.cpuFilePath) - fmt.Fprintf(p.logWriter, "Memory profile: %v\n", p.memFilePath) -}