internal/execute/tsc.go (298 lines of code) (raw):

package execute import ( "context" "fmt" "strings" "github.com/microsoft/typescript-go/internal/ast" "github.com/microsoft/typescript-go/internal/collections" "github.com/microsoft/typescript-go/internal/compiler" "github.com/microsoft/typescript-go/internal/core" "github.com/microsoft/typescript-go/internal/diagnostics" "github.com/microsoft/typescript-go/internal/execute/build" "github.com/microsoft/typescript-go/internal/execute/incremental" "github.com/microsoft/typescript-go/internal/execute/tsc" "github.com/microsoft/typescript-go/internal/format" "github.com/microsoft/typescript-go/internal/jsonutil" "github.com/microsoft/typescript-go/internal/locale" "github.com/microsoft/typescript-go/internal/parser" "github.com/microsoft/typescript-go/internal/pprof" "github.com/microsoft/typescript-go/internal/tsoptions" "github.com/microsoft/typescript-go/internal/tspath" ) func CommandLine(sys tsc.System, commandLineArgs []string, testing tsc.CommandLineTesting) tsc.CommandLineResult { if len(commandLineArgs) > 0 { 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 { locale := buildCommand.Locale() reportDiagnostic := tsc.CreateDiagnosticReporter(sys, sys.Writer(), locale, buildCommand.CompilerOptions) 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, locale) tsc.PrintBuildHelp(sys, locale, 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 := "" locale := commandLine.Locale() reportDiagnostic := tsc.CreateDiagnosticReporter(sys, sys.Writer(), locale, commandLine.CompilerOptions()) 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() { tsc.WriteConfigFile(sys, locale, reportDiagnostic, commandLine.Raw.(*collections.OrderedMap[string, any])) return tsc.CommandLineResult{Status: tsc.ExitStatusSuccess} } if commandLine.CompilerOptions().Version.IsTrue() { tsc.PrintVersion(sys, locale) return tsc.CommandLineResult{Status: tsc.ExitStatusSuccess} } if commandLine.CompilerOptions().Help.IsTrue() || commandLine.CompilerOptions().All.IsTrue() { tsc.PrintHelp(sys, locale, 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 !commandLine.CompilerOptions().IgnoreConfig.IsTrue() || len(commandLine.FileNames()) == 0 { searchPath := tspath.NormalizePath(sys.GetCurrentDirectory()) configFileName = findConfigFile(searchPath, sys.FS().FileExists, "tsconfig.json") if len(commandLine.FileNames()) != 0 { if configFileName != "" { // Error to not specify config file reportDiagnostic(ast.NewCompilerDiagnostic(diagnostics.X_tsconfig_json_is_present_but_will_not_be_loaded_if_files_are_specified_on_commandline_Use_ignoreConfig_to_skip_this_error)) return tsc.CommandLineResult{Status: tsc.ExitStatusDiagnosticsPresent_OutputsSkipped} } } else if configFileName == "" { 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, locale) tsc.PrintHelp(sys, locale, 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() var commandLineRaw *collections.OrderedMap[string, any] if raw, ok := commandLine.Raw.(*collections.OrderedMap[string, any]); ok { // Wrap command line options in a "compilerOptions" key to match tsconfig.json structure wrapped := &collections.OrderedMap[string, any]{} wrapped.Set("compilerOptions", raw) commandLineRaw = wrapped } configParseResult, errors := tsoptions.GetParsedCommandLineOfConfigFile(configFileName, compilerOptionsFromCommandLine, commandLineRaw, 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(), locale, commandLine.CompilerOptions()) } reportErrorSummary := tsc.CreateReportErrorSummary(sys, locale, 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, compilerOptionsFromCommandLine, 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, locale locale.Locale, testing tsc.CommandLineTesting) func(msg *diagnostics.Message, args ...any) { return tsc.GetTraceWithWriterFromSys(sys.Writer(), locale, 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, config.Locale(), 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, config.Locale(), 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, "", " ") }