377 lines
14 KiB
Go
377 lines
14 KiB
Go
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/json"
|
|
"github.com/microsoft/typescript-go/internal/locale"
|
|
"github.com/microsoft/typescript-go/internal/ls/lsutil"
|
|
"github.com/microsoft/typescript-go/internal/parser"
|
|
"github.com/microsoft/typescript-go/internal/pprof"
|
|
"github.com/microsoft/typescript-go/internal/tracing"
|
|
"github.com/microsoft/typescript-go/internal/tsoptions"
|
|
"github.com/microsoft/typescript-go/internal/tspath"
|
|
)
|
|
|
|
func startTracingIfNeeded(sys tsc.System, config *tsoptions.ParsedCommandLine, testing tsc.CommandLineTesting) *tracing.Tracing {
|
|
traceDir := config.CompilerOptions().GenerateTrace
|
|
if traceDir == "" {
|
|
return nil
|
|
}
|
|
configFilePath := ""
|
|
if config.ConfigFile != nil && config.ConfigFile.SourceFile != nil {
|
|
configFilePath = config.ConfigFile.SourceFile.FileName()
|
|
}
|
|
tr, err := tracing.StartTracing(sys.FS(), traceDir, configFilePath, testing != nil)
|
|
if err != nil {
|
|
fmt.Fprintf(sys.Writer(), "Warning: Failed to start tracing: %v\n", err)
|
|
}
|
|
return tr
|
|
}
|
|
|
|
func stopTracing(sys tsc.System, tr *tracing.Tracing) {
|
|
if tr == nil {
|
|
return
|
|
}
|
|
if err := tr.StopTracing(); err != nil {
|
|
fmt.Fprintf(sys.Writer(), "Warning: Failed to stop tracing: %v\n", err)
|
|
}
|
|
}
|
|
|
|
func CommandLine(ctx context.Context, 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(ctx, sys, tsoptions.ParseBuildCommandLine(commandLineArgs, sys), testing)
|
|
// case "-f":
|
|
// return fmtMain(sys, commandLineArgs[1], commandLineArgs[1])
|
|
}
|
|
}
|
|
|
|
return tscCompilation(ctx, sys, tsoptions.ParseCommandLine(commandLineArgs, sys), testing)
|
|
}
|
|
|
|
func fmtMain(sys tsc.System, input, output string) tsc.ExitStatus {
|
|
ctx := format.WithFormatCodeSettings(context.Background(), lsutil.GetDefaultFormatCodeSettings(), "\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,
|
|
}, text, core.GetScriptKindFromFileName(string(pathified)))
|
|
edits := format.FormatDocument(ctx, sourceFile)
|
|
newText := core.ApplyBulkEdits(text, edits)
|
|
|
|
if err := sys.FS().WriteFile(output, newText); err != nil {
|
|
fmt.Fprintln(sys.Writer(), err.Error())
|
|
return tsc.ExitStatusNotImplemented
|
|
}
|
|
return tsc.ExitStatusSuccess
|
|
}
|
|
|
|
func tscBuildCompilation(ctx context.Context, 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(ctx)
|
|
}
|
|
|
|
func tscCompilation(ctx context.Context, 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
|
|
var commandLineRaw *collections.OrderedMap[string, any]
|
|
if configFileName != "" {
|
|
configStart := sys.Now()
|
|
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, configFileName)
|
|
return tsc.CommandLineResult{Status: tsc.ExitStatusSuccess}
|
|
}
|
|
if configForCompilation.CompilerOptions().Watch.IsTrue() {
|
|
watcher := createWatcher(
|
|
sys,
|
|
configForCompilation,
|
|
compilerOptionsFromCommandLine,
|
|
commandLineRaw,
|
|
reportDiagnostic,
|
|
reportErrorSummary,
|
|
testing,
|
|
)
|
|
watcher.start(ctx)
|
|
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)
|
|
|
|
tr := startTracingIfNeeded(sys, config, testing)
|
|
|
|
parseStart := sys.Now()
|
|
program := compiler.NewProgram(compiler.ProgramOptions{
|
|
Config: config,
|
|
Host: host,
|
|
Tracing: tr,
|
|
})
|
|
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,
|
|
Tracing: tr,
|
|
})
|
|
|
|
stopTracing(sys, tr)
|
|
|
|
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))
|
|
|
|
tr := startTracingIfNeeded(sys, config, testing)
|
|
|
|
parseStart := sys.Now()
|
|
program := compiler.NewProgram(compiler.ProgramOptions{
|
|
Config: config,
|
|
Host: host,
|
|
Tracing: tr,
|
|
})
|
|
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,
|
|
Tracing: tr,
|
|
})
|
|
|
|
stopTracing(sys, tr)
|
|
|
|
return tsc.CommandLineResult{
|
|
Status: result.Status,
|
|
}
|
|
}
|
|
|
|
func showConfig(sys tsc.System, config *tsoptions.ParsedCommandLine, configFileName string) {
|
|
tsConfig := tsoptions.ConvertToTSConfig(config, configFileName)
|
|
_ = json.MarshalIndentWrite(sys.Writer(), tsConfig, "", " ")
|
|
}
|