vendor tsgo
This commit is contained in:
80
tools/tsgo/internal/execute/tsc/compile.go
Normal file
80
tools/tsgo/internal/execute/tsc/compile.go
Normal 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
|
||||
}
|
||||
176
tools/tsgo/internal/execute/tsc/diagnostics.go
Normal file
176
tools/tsgo/internal/execute/tsc/diagnostics.go
Normal 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)
|
||||
}
|
||||
}
|
||||
152
tools/tsgo/internal/execute/tsc/emit.go
Normal file
152
tools/tsgo/internal/execute/tsc/emit.go
Normal 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())
|
||||
}
|
||||
}
|
||||
}
|
||||
45
tools/tsgo/internal/execute/tsc/extendedconfigcache.go
Normal file
45
tools/tsgo/internal/execute/tsc/extendedconfigcache.go
Normal 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
|
||||
}
|
||||
118
tools/tsgo/internal/execute/tsc/extendedconfigcache_test.go
Normal file
118
tools/tsgo/internal/execute/tsc/extendedconfigcache_test.go
Normal 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")
|
||||
}
|
||||
426
tools/tsgo/internal/execute/tsc/help.go
Normal file
426
tools/tsgo/internal/execute/tsc/help.go
Normal 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, "")
|
||||
}
|
||||
215
tools/tsgo/internal/execute/tsc/init.go
Normal file
215
tools/tsgo/internal/execute/tsc/init.go
Normal 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")
|
||||
}
|
||||
157
tools/tsgo/internal/execute/tsc/statistics.go
Normal file
157
tools/tsgo/internal/execute/tsc/statistics.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user