vendor tsgo

This commit is contained in:
2026-07-09 16:50:43 -04:00
parent c06ea2e5a4
commit 98978e4930
5804 changed files with 1556156 additions and 101 deletions

View File

@@ -0,0 +1,80 @@
package tsc
import (
"io"
"time"
"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/diagnostics"
"github.com/microsoft/typescript-go/internal/execute/incremental"
"github.com/microsoft/typescript-go/internal/locale"
"github.com/microsoft/typescript-go/internal/tspath"
"github.com/microsoft/typescript-go/internal/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_OutputsSkipped ExitStatus = 1
ExitStatusDiagnosticsPresent_OutputsGenerated 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, locale locale.Locale) func(msg *diagnostics.Message, args ...any)
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
}

View File

@@ -0,0 +1,176 @@
package tsc
import (
"fmt"
"io"
"strings"
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/diagnosticwriter"
"github.com/microsoft/typescript-go/internal/locale"
"github.com/microsoft/typescript-go/internal/tspath"
)
func getFormatOptsOfSys(sys System, locale locale.Locale) *diagnosticwriter.FormattingOptions {
return &diagnosticwriter.FormattingOptions{
NewLine: "\n",
ComparePathsOptions: tspath.ComparePathsOptions{
CurrentDirectory: sys.GetCurrentDirectory(),
UseCaseSensitiveFileNames: sys.FS().UseCaseSensitiveFileNames(),
},
Locale: locale,
}
}
type DiagnosticReporter = func(*ast.Diagnostic)
func QuietDiagnosticReporter(diagnostic *ast.Diagnostic) {}
func CreateDiagnosticReporter(sys System, w io.Writer, locale locale.Locale, options *core.CompilerOptions) DiagnosticReporter {
if options.Quiet.IsTrue() {
return QuietDiagnosticReporter
}
formatOpts := getFormatOptsOfSys(sys, locale)
if shouldBePretty(sys, options) {
return func(diagnostic *ast.Diagnostic) {
diagnosticwriter.FormatDiagnosticWithColorAndContext(w, diagnosticwriter.WrapASTDiagnostic(diagnostic), formatOpts)
fmt.Fprint(w, formatOpts.NewLine)
}
}
return func(diagnostic *ast.Diagnostic) {
diagnosticwriter.WriteFormatDiagnostic(w, diagnosticwriter.WrapASTDiagnostic(diagnostic), formatOpts)
}
}
func defaultIsPretty(sys System) bool {
if sys.GetEnvironmentVariable("NO_COLOR") != "" {
return false
}
if sys.GetEnvironmentVariable("FORCE_COLOR") != "" {
return true
}
return sys.WriteOutputIsTTY()
}
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, locale locale.Locale, options *core.CompilerOptions) DiagnosticsReporter {
if shouldBePretty(sys, options) {
formatOpts := getFormatOptsOfSys(sys, locale)
return func(diagnostics []*ast.Diagnostic) {
diagnosticwriter.WriteErrorSummaryText(sys.Writer(), diagnosticwriter.FromASTDiagnostics(diagnostics), formatOpts)
}
}
return QuietDiagnosticsReporter
}
func CreateBuilderStatusReporter(sys System, w io.Writer, locale locale.Locale, options *core.CompilerOptions, testing CommandLineTesting) DiagnosticReporter {
if options.Quiet.IsTrue() {
return QuietDiagnosticReporter
}
formatOpts := getFormatOptsOfSys(sys, locale)
writeStatus := core.IfElse(shouldBePretty(sys, options), diagnosticwriter.FormatDiagnosticsStatusWithColorAndTime, diagnosticwriter.FormatDiagnosticsStatusAndTime)
return func(diagnostic *ast.Diagnostic) {
writerDiagnostic := diagnosticwriter.WrapASTDiagnostic(diagnostic)
if testing != nil {
testing.OnBuildStatusReportStart(w)
defer testing.OnBuildStatusReportEnd(w)
}
writeStatus(w, sys.Now().Format("03:04:05 PM"), writerDiagnostic, formatOpts)
fmt.Fprint(w, formatOpts.NewLine, formatOpts.NewLine)
}
}
func CreateWatchStatusReporter(sys System, locale locale.Locale, options *core.CompilerOptions, testing CommandLineTesting) DiagnosticReporter {
formatOpts := getFormatOptsOfSys(sys, locale)
writeStatus := core.IfElse(shouldBePretty(sys, options), diagnosticwriter.FormatDiagnosticsStatusWithColorAndTime, diagnosticwriter.FormatDiagnosticsStatusAndTime)
return func(diagnostic *ast.Diagnostic) {
writerDiagnostic := diagnosticwriter.WrapASTDiagnostic(diagnostic)
writer := sys.Writer()
if testing != nil {
testing.OnWatchStatusReportStart()
defer testing.OnWatchStatusReportEnd()
}
diagnosticwriter.TryClearScreen(writer, writerDiagnostic, options)
writeStatus(writer, sys.Now().Format("03:04:05 PM"), writerDiagnostic, formatOpts)
fmt.Fprint(writer, formatOpts.NewLine, formatOpts.NewLine)
}
}

View File

@@ -0,0 +1,152 @@
package tsc
import (
"context"
"fmt"
"io"
"runtime"
"time"
"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/diagnostics"
"github.com/microsoft/typescript-go/internal/locale"
"github.com/microsoft/typescript-go/internal/tracing"
"github.com/microsoft/typescript-go/internal/tsoptions"
"github.com/microsoft/typescript-go/internal/tspath"
)
func GetTraceWithWriterFromSys(w io.Writer, locale locale.Locale, testing CommandLineTesting) func(msg *diagnostics.Message, args ...any) {
if testing == nil {
return func(msg *diagnostics.Message, args ...any) {
fmt.Fprintln(w, msg.Localize(locale, args...))
}
} else {
return testing.GetTrace(w, locale)
}
}
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]
Tracing *tracing.Tracing
}
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.
if tr := input.Tracing; tr != nil {
defer tr.Push(tracing.PhaseBind, "bindSourceFiles", nil, true)()
}
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 {
if tr := input.Tracing; tr != nil {
defer tr.Push(tracing.PhaseCheck, "checkSourceFiles", nil, true)()
}
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, input.Config.Locale())
} else if options.ListFiles.IsTrue() || options.ListFilesOnly.IsTrue() {
for _, file := range input.Program.GetSourceFiles() {
fmt.Fprintln(input.Writer, file.FileName())
}
}
}

View File

@@ -0,0 +1,45 @@
package tsc
import (
"sync"
"github.com/microsoft/typescript-go/internal/collections"
"github.com/microsoft/typescript-go/internal/tsoptions"
"github.com/microsoft/typescript-go/internal/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, resolutionStack []tspath.Path, host tsoptions.ParseConfigHost) *tsoptions.ExtendedConfigCacheEntry {
entry, loaded := e.loadOrStoreNewLockedEntry(path)
defer entry.mu.Unlock()
if !loaded {
entry.ExtendedConfigCacheEntry = tsoptions.ParseExtendedConfig(fileName, path, resolutionStack, host, e)
}
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
}

View File

@@ -0,0 +1,118 @@
package tsc_test
import (
"testing"
"github.com/microsoft/typescript-go/internal/execute/tsc"
"github.com/microsoft/typescript-go/internal/tsoptions"
"github.com/microsoft/typescript-go/internal/vfs"
"github.com/microsoft/typescript-go/internal/vfs/vfstest"
)
type testParseConfigHost struct {
fs vfs.FS
cwd string
}
func (h *testParseConfigHost) FS() vfs.FS { return h.fs }
func (h *testParseConfigHost) GetCurrentDirectory() string { return h.cwd }
func TestExtendedConfigCacheExtendsCircularity(t *testing.T) {
t.Parallel()
t.Run("self-referencing extends", func(t *testing.T) {
t.Parallel()
// Regression test: a tsconfig extends cycle should produce an error,
// not a deadlock when using the tsc ExtendedConfigCache.
files := map[string]any{
"/project/tsconfig.json": `{"extends": "./base.json"}`,
"/project/base.json": `{"extends": "./base.json"}`,
"/project/main.ts": `// Hello World!`,
}
fs := vfstest.FromMap(files, false /*useCaseSensitiveFileNames*/)
host := &testParseConfigHost{fs: fs, cwd: "/project"}
cache := &tsc.ExtendedConfigCache{}
cmd, _ := tsoptions.GetParsedCommandLineOfConfigFile("/project/tsconfig.json", nil, nil, host, cache)
if cmd == nil {
t.Fatal("expected non-nil ParsedCommandLine")
}
assertHasCircularityDiagnostic(t, cmd)
})
t.Run("mutual extends cycle", func(t *testing.T) {
t.Parallel()
// Two config files that extend each other.
files := map[string]any{
"/project/tsconfig.json": `{"extends": "./other.json"}`,
"/project/other.json": `{"extends": "./tsconfig.json"}`,
"/project/main.ts": `// Hello World!`,
}
fs := vfstest.FromMap(files, false /*useCaseSensitiveFileNames*/)
host := &testParseConfigHost{fs: fs, cwd: "/project"}
cache := &tsc.ExtendedConfigCache{}
cmd, _ := tsoptions.GetParsedCommandLineOfConfigFile("/project/tsconfig.json", nil, nil, host, cache)
if cmd == nil {
t.Fatal("expected non-nil ParsedCommandLine")
}
assertHasCircularityDiagnostic(t, cmd)
})
t.Run("case-insensitive self-referencing extends", func(t *testing.T) {
t.Parallel()
// On a case-insensitive FS, ./Base.json and ./base.json resolve to the same
// cache entry. The cycle check must use canonical paths to avoid deadlock.
files := map[string]any{
"/project/tsconfig.json": `{"extends": "./Base.json"}`,
"/project/base.json": `{"extends": "./base.json"}`,
"/project/main.ts": `// Hello World!`,
}
fs := vfstest.FromMap(files, false /*useCaseSensitiveFileNames*/)
host := &testParseConfigHost{fs: fs, cwd: "/project"}
cache := &tsc.ExtendedConfigCache{}
cmd, _ := tsoptions.GetParsedCommandLineOfConfigFile("/project/tsconfig.json", nil, nil, host, cache)
if cmd == nil {
t.Fatal("expected non-nil ParsedCommandLine")
}
assertHasCircularityDiagnostic(t, cmd)
})
}
func TestExtendedConfigCacheNullExtendsDoesNotPanic(t *testing.T) {
t.Parallel()
files := map[string]any{
"/project/tsconfig.json": `{"extends": null}`,
"/project/main.ts": `// Hello World!`,
}
fs := vfstest.FromMap(files, false /*useCaseSensitiveFileNames*/)
host := &testParseConfigHost{fs: fs, cwd: "/project"}
cache := &tsc.ExtendedConfigCache{}
cmd, _ := tsoptions.GetParsedCommandLineOfConfigFile("/project/tsconfig.json", nil, nil, host, cache)
if cmd == nil {
t.Fatal("expected non-nil ParsedCommandLine")
}
if len(cmd.Errors) == 0 {
t.Fatal("expected diagnostics for invalid null extends")
}
}
func assertHasCircularityDiagnostic(t *testing.T, cmd *tsoptions.ParsedCommandLine) {
t.Helper()
for _, d := range cmd.Errors {
if d != nil && d.Code() == 18000 {
return
}
}
t.Error("expected circularity diagnostic (code 18000), but none was found")
}

View File

@@ -0,0 +1,426 @@
package tsc
import (
"fmt"
"slices"
"strings"
"github.com/microsoft/typescript-go/internal/collections"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/diagnostics"
"github.com/microsoft/typescript-go/internal/locale"
"github.com/microsoft/typescript-go/internal/tsoptions"
)
func PrintVersion(sys System, locale locale.Locale) {
fmt.Fprintln(sys.Writer(), diagnostics.Version_0.Localize(locale, core.Version()))
}
func PrintHelp(sys System, locale locale.Locale, commandLine *tsoptions.ParsedCommandLine) {
if commandLine.CompilerOptions().All.IsFalseOrUnknown() {
printEasyHelp(sys, locale, getOptionsForHelp(commandLine))
} else {
printAllHelp(sys, locale, 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, locale locale.Locale, 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.Localize(locale), "\n", "\n")
}
msg := diagnostics.X_tsc_Colon_The_TypeScript_Compiler.Localize(locale) + " - " + diagnostics.Version_0.Localize(locale, core.Version())
output = append(output, getHeader(sys, msg)...)
output = append(output, colors.bold(diagnostics.COMMON_COMMANDS.Localize(locale)), "\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, locale, diagnostics.COMMAND_LINE_FLAGS.Localize(locale), cliCommands /*subCategory*/, false /*beforeOptionsDescription*/, nil /*afterOptionsDescription*/, nil)...)
after := diagnostics.You_can_learn_about_all_of_the_compiler_options_at_0.Localize(locale, "https://aka.ms/tsc")
output = append(output, generateSectionOptionsOutput(sys, locale, diagnostics.COMMON_COMPILER_OPTIONS.Localize(locale), configOpts /*subCategory*/, false /*beforeOptionsDescription*/, nil, &after)...)
for _, chunk := range output {
fmt.Fprint(sys.Writer(), chunk)
}
}
func printAllHelp(sys System, locale locale.Locale, options []*tsoptions.CommandLineOption) {
var output []string
msg := diagnostics.X_tsc_Colon_The_TypeScript_Compiler.Localize(locale) + " - " + diagnostics.Version_0.Localize(locale, core.Version())
output = append(output, getHeader(sys, msg)...)
// ALL COMPILER OPTIONS section
afterCompilerOptions := diagnostics.You_can_learn_about_all_of_the_compiler_options_at_0.Localize(locale, "https://aka.ms/tsc")
output = append(output, generateSectionOptionsOutput(sys, locale, diagnostics.ALL_COMPILER_OPTIONS.Localize(locale), options, true, nil, &afterCompilerOptions)...)
// WATCH OPTIONS section
beforeWatchOptions := diagnostics.Including_watch_w_will_start_watching_the_current_project_for_the_file_changes_Once_set_you_can_config_watch_mode_with_Colon.Localize(locale)
output = append(output, generateSectionOptionsOutput(sys, locale, diagnostics.WATCH_OPTIONS.Localize(locale), tsoptions.OptionsForWatch, false, &beforeWatchOptions, nil)...)
// BUILD OPTIONS section
beforeBuildOptions := 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.Localize(locale, "https://aka.ms/tsc-composite-builds")
buildOptions := core.Filter(tsoptions.OptionsForBuild, func(option *tsoptions.CommandLineOption) bool {
return option != &tsoptions.TscBuildOption
})
output = append(output, generateSectionOptionsOutput(sys, locale, diagnostics.BUILD_OPTIONS.Localize(locale), buildOptions, false, &beforeBuildOptions, nil)...)
for _, chunk := range output {
fmt.Fprint(sys.Writer(), chunk)
}
}
func PrintBuildHelp(sys System, locale locale.Locale, buildOptions []*tsoptions.CommandLineOption) {
var output []string
output = append(output, getHeader(sys, diagnostics.X_tsc_Colon_The_TypeScript_Compiler.Localize(locale)+" - "+diagnostics.Version_0.Localize(locale, 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.Localize(locale, "https://aka.ms/tsc-composite-builds")
options := core.Filter(buildOptions, func(option *tsoptions.CommandLineOption) bool {
return option != &tsoptions.TscBuildOption
})
output = append(output, generateSectionOptionsOutput(sys, locale, diagnostics.BUILD_OPTIONS.Localize(locale), options, false, &before, nil)...)
for _, chunk := range output {
fmt.Fprint(sys.Writer(), chunk)
}
}
func generateSectionOptionsOutput(
sys System,
locale locale.Locale,
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, locale, options)...)
if afterOptionsDescription != nil {
output = append(output, *afterOptionsDescription, "\n", "\n")
}
return output
}
categoryMap := make(map[string][]*tsoptions.CommandLineOption)
var categoryOrder []string
for _, option := range options {
if option.Category == nil {
continue
}
curCategory := option.Category.Localize(locale)
if _, exists := categoryMap[curCategory]; !exists {
categoryOrder = append(categoryOrder, curCategory)
}
categoryMap[curCategory] = append(categoryMap[curCategory], option)
}
for _, key := range categoryOrder {
value := categoryMap[key]
output = append(output, "### ", key, "\n", "\n")
output = append(output, generateGroupOptionOutput(sys, locale, value)...)
}
if afterOptionsDescription != nil {
output = append(output, *afterOptionsDescription, "\n", "\n")
}
return output
}
func generateGroupOptionOutput(sys System, locale locale.Locale, 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, locale, 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,
locale locale.Locale,
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(sys, locale, option)
var defaultValueDescription string
if msg, ok := option.DefaultValueDescription.(*diagnostics.Message); ok && msg != nil {
defaultValueDescription = msg.Localize(locale)
} 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.Localize(locale)
}
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.Localize(locale), 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.Localize(locale))
}
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.Localize(locale), " ", 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(sys System, locale locale.Locale, option *tsoptions.CommandLineOption) *valueCandidate {
// option.type might be "string" | "number" | "boolean" | "object" | "list" | Map<string, number | string>
// 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<string, number | string> -- 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.Localize(locale)
case tsoptions.CommandLineOptionTypeList:
res.valueType = diagnostics.X_one_or_more_Colon.Localize(locale)
default:
res.valueType = diagnostics.X_one_of_Colon.Localize(locale)
}
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<string, number | string>
// 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, "")
}

View File

@@ -0,0 +1,215 @@
package tsc
import (
"fmt"
"reflect"
"slices"
"strings"
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/collections"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/diagnostics"
"github.com/microsoft/typescript-go/internal/json"
"github.com/microsoft/typescript-go/internal/locale"
"github.com/microsoft/typescript-go/internal/tsoptions"
"github.com/microsoft/typescript-go/internal/tspath"
)
func WriteConfigFile(sys System, locale locale.Locale, reportDiagnostic DiagnosticReporter, options *collections.OrderedMap[string, any]) {
getCurrentDirectory := sys.GetCurrentDirectory()
file := tspath.NormalizePath(tspath.CombinePaths(getCurrentDirectory, "tsconfig.json"))
if sys.FS().FileExists(file) {
reportDiagnostic(ast.NewCompilerDiagnostic(diagnostics.A_tsconfig_json_file_is_already_defined_at_Colon_0, file))
} else {
_ = sys.FS().WriteFile(file, generateTSConfig(options, locale))
output := []string{"\n"}
output = append(output, getHeader(sys, "Created a new tsconfig.json")...)
output = append(output, "You can learn more at https://aka.ms/tsconfig", "\n")
fmt.Fprint(sys.Writer(), strings.Join(output, ""))
}
}
func generateTSConfig(options *collections.OrderedMap[string, any], locale locale.Locale) string {
const tab = " "
var result []string
allSetOptions := make([]string, 0, options.Size())
for k := range options.Keys() {
if k != "init" && k != "help" && k != "watch" {
allSetOptions = append(allSetOptions, k)
}
}
emitHeader := func(header *diagnostics.Message) {
result = append(result, tab+tab+"// "+header.Localize(locale))
}
newline := func() {
result = append(result, "")
}
push := func(args ...string) {
result = append(result, args...)
}
formatSingleValue := func(value any, enumMap *collections.OrderedMap[string, any]) string {
if enumMap != nil {
var found bool
for k, v := range enumMap.Entries() {
if value == v {
value = k
found = true
break
}
}
if !found {
panic(fmt.Sprintf("No matching value of %v", value))
}
}
b, err := json.MarshalIndent(value, "", "")
if err != nil {
panic(fmt.Sprintf("should not happen: %v", err))
}
return string(b)
}
formatValueOrArray := func(settingName string, value any) string {
var option *tsoptions.CommandLineOption
for _, decl := range tsoptions.OptionsDeclarations {
if decl.Name == settingName {
option = decl
}
}
if option == nil {
panic(`No option named ` + settingName)
}
rval := reflect.ValueOf(value)
if rval.Kind() == reflect.Slice {
var enumMap *collections.OrderedMap[string, any]
if elemOption := option.Elements(); elemOption != nil {
enumMap = elemOption.EnumMap()
}
var elems []string
for i := range rval.Len() {
elems = append(elems, formatSingleValue(rval.Index(i).Interface(), enumMap))
}
return `[` + strings.Join(elems, ", ") + `]`
} else {
return formatSingleValue(value, option.EnumMap())
}
}
// commentedNever': Never comment this out
// commentedAlways': Always comment this out, even if it's on commandline
// commentedOptional': Comment out unless it's on commandline
type commented int
const (
commentedNever commented = iota
commentedAlways
commentedOptional
)
emitOption := func(setting string, defaultValue any, commented commented) {
if commented > 2 {
panic("should not happen: invalid `commented`, must be a bug.")
}
existingOptionIndex := slices.Index(allSetOptions, setting)
if existingOptionIndex >= 0 {
allSetOptions = slices.Delete(allSetOptions, existingOptionIndex, existingOptionIndex+1)
}
var comment bool
switch commented {
case commentedAlways:
comment = true
case commentedNever:
comment = false
default:
comment = !options.Has(setting)
}
value, ok := options.Get(setting)
if !ok {
value = defaultValue
}
if comment {
push(tab + tab + `// "` + setting + `": ` + formatValueOrArray(setting, value) + `,`)
} else {
push(tab + tab + `"` + setting + `": ` + formatValueOrArray(setting, value) + `,`)
}
}
push("{")
push(tab + `// ` + diagnostics.Visit_https_Colon_Slash_Slashaka_ms_Slashtsconfig_to_read_more_about_this_file.Localize(locale))
push(tab + `"compilerOptions": {`)
emitHeader(diagnostics.File_Layout)
emitOption("rootDir", "./src", commentedOptional)
emitOption("outDir", "./dist", commentedOptional)
newline()
emitHeader(diagnostics.Environment_Settings)
emitHeader(diagnostics.See_also_https_Colon_Slash_Slashaka_ms_Slashtsconfig_Slashmodule)
emitOption("module", core.ModuleKindNodeNext, commentedNever)
emitOption("target", core.ScriptTargetESNext, commentedNever)
emitOption("types", []any{}, commentedNever)
if lib, ok := options.Get("lib"); ok {
emitOption("lib", lib, commentedNever)
}
emitHeader(diagnostics.For_nodejs_Colon)
push(tab + tab + `// "lib": ["esnext"],`)
push(tab + tab + `// "types": ["node"],`)
emitHeader(diagnostics.X_and_npm_install_D_types_Slashnode)
newline()
emitHeader(diagnostics.Other_Outputs)
emitOption("sourceMap" /*defaultValue*/, true, commentedNever)
emitOption("declaration" /*defaultValue*/, true, commentedNever)
emitOption("declarationMap" /*defaultValue*/, true, commentedNever)
newline()
emitHeader(diagnostics.Stricter_Typechecking_Options)
emitOption("noUncheckedIndexedAccess" /*defaultValue*/, true, commentedNever)
emitOption("exactOptionalPropertyTypes" /*defaultValue*/, true, commentedNever)
newline()
emitHeader(diagnostics.Style_Options)
emitOption("noImplicitReturns" /*defaultValue*/, true, commentedOptional)
emitOption("noImplicitOverride" /*defaultValue*/, true, commentedOptional)
emitOption("noUnusedLocals" /*defaultValue*/, true, commentedOptional)
emitOption("noUnusedParameters" /*defaultValue*/, true, commentedOptional)
emitOption("noFallthroughCasesInSwitch" /*defaultValue*/, true, commentedOptional)
emitOption("noPropertyAccessFromIndexSignature" /*defaultValue*/, true, commentedOptional)
newline()
emitHeader(diagnostics.Recommended_Options)
emitOption("strict" /*defaultValue*/, true, commentedNever)
emitOption("jsx", core.JsxEmitReactJSX, commentedNever)
emitOption("verbatimModuleSyntax" /*defaultValue*/, true, commentedNever)
emitOption("isolatedModules" /*defaultValue*/, true, commentedNever)
emitOption("noUncheckedSideEffectImports" /*defaultValue*/, true, commentedNever)
emitOption("moduleDetection", core.ModuleDetectionKindForce, commentedNever)
emitOption("skipLibCheck" /*defaultValue*/, true, commentedNever)
// Write any user-provided options we haven't already
if len(allSetOptions) > 0 {
newline()
for len(allSetOptions) > 0 {
emitOption(allSetOptions[0], options.GetOrZero(allSetOptions[0]), commentedNever)
}
}
push(tab + "}")
push(`}`)
push(``)
return strings.Join(result, "\n")
}

View File

@@ -0,0 +1,157 @@
package tsc
import (
"fmt"
"io"
"runtime"
"strconv"
"time"
"github.com/microsoft/typescript-go/internal/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
}