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

320 lines
12 KiB
Go

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, "", " ")
}