From c6294030f0bf4b4fb2761fec4700803f3e114ff6 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 20 Aug 2026 21:28:32 -0700 Subject: [PATCH 1/3] Handle FORCE_COLOR values like Node --- tsc/cmd/tsc/sys.go | 4 + tsc/internal/execute/tsc/compile.go | 1 + tsc/internal/execute/tsc/diagnostics.go | 12 ++- tsc/internal/execute/tsc/diagnostics_test.go | 72 ++++++++++++++++++ tsc/internal/execute/tsc/emit_test.go | 12 ++- tsc/internal/execute/tsctests/sys.go | 5 ++ tsc/internal/execute/tsctests/tsc_test.go | 2 +- ...t.js => FORCE_COLOR-overrides-NO_COLOR.js} | 76 +++++++++---------- 8 files changed, 141 insertions(+), 43 deletions(-) create mode 100644 tsc/internal/execute/tsc/diagnostics_test.go rename tsc/testdata/baselines/reference/tsc/commandLine/{does-not-add-color-when-NO_COLOR-is-set-even-if-FORCE_COLOR-is-set.js => FORCE_COLOR-overrides-NO_COLOR.js} (84%) diff --git a/tsc/cmd/tsc/sys.go b/tsc/cmd/tsc/sys.go index 1c3c385ad0833..1d7dab0cbd5ec 100644 --- a/tsc/cmd/tsc/sys.go +++ b/tsc/cmd/tsc/sys.go @@ -65,6 +65,10 @@ func (s *osSys) GetEnvironmentVariable(name string) string { return os.Getenv(name) } +func (s *osSys) LookupEnvironmentVariable(name string) (string, bool) { + return os.LookupEnv(name) +} + func (s *osSys) Spawn(command []string, dir string, stderr io.Writer) (io.ReadWriteCloser, error) { return spawnProcess(command, dir, stderr) } diff --git a/tsc/internal/execute/tsc/compile.go b/tsc/internal/execute/tsc/compile.go index 3b94355a5ec0e..c61a6b938620b 100644 --- a/tsc/internal/execute/tsc/compile.go +++ b/tsc/internal/execute/tsc/compile.go @@ -28,6 +28,7 @@ type System interface { WriteOutputIsTTY() bool GetWidthOfTerminal() int GetEnvironmentVariable(name string) string + LookupEnvironmentVariable(name string) (string, bool) Spawn(command []string, dir string, stderr io.Writer) (io.ReadWriteCloser, error) Now() time.Time diff --git a/tsc/internal/execute/tsc/diagnostics.go b/tsc/internal/execute/tsc/diagnostics.go index 33fbd9fc2e2c8..6d75e8d56c0b5 100644 --- a/tsc/internal/execute/tsc/diagnostics.go +++ b/tsc/internal/execute/tsc/diagnostics.go @@ -44,11 +44,19 @@ func CreateDiagnosticReporter(sys System, w io.Writer, locale locale.Locale, opt } func defaultIsPretty(sys System) bool { + if forceColor, ok := sys.LookupEnvironmentVariable("FORCE_COLOR"); ok { + switch forceColor { + case "", "1", "2", "3", "true": + return true + default: + return false + } + } if sys.GetEnvironmentVariable("NO_COLOR") != "" { return false } - if sys.GetEnvironmentVariable("FORCE_COLOR") != "" { - return true + if sys.GetEnvironmentVariable("TERM") == "dumb" { + return false } return sys.WriteOutputIsTTY() } diff --git a/tsc/internal/execute/tsc/diagnostics_test.go b/tsc/internal/execute/tsc/diagnostics_test.go new file mode 100644 index 0000000000000..95be81f4d4de5 --- /dev/null +++ b/tsc/internal/execute/tsc/diagnostics_test.go @@ -0,0 +1,72 @@ +package tsc + +import "testing" + +type colorTestSystem struct { + *timingTestSystem + env map[string]string + tty bool +} + +func (s *colorTestSystem) WriteOutputIsTTY() bool { + return s.tty +} + +func (s *colorTestSystem) GetEnvironmentVariable(name string) string { + return s.env[name] +} + +func (s *colorTestSystem) LookupEnvironmentVariable(name string) (string, bool) { + value, ok := s.env[name] + return value, ok +} + +func TestDefaultIsPretty(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + env map[string]string + tty bool + expected bool + }{ + {name: "TTY", tty: true, expected: true}, + {name: "non-TTY", tty: false, expected: false}, + {name: "NO_COLOR", env: map[string]string{"NO_COLOR": "1"}, tty: true, expected: false}, + {name: "empty NO_COLOR", env: map[string]string{"NO_COLOR": ""}, tty: true, expected: true}, + {name: "TERM=dumb", env: map[string]string{"TERM": "dumb"}, tty: true, expected: false}, + {name: "empty FORCE_COLOR", env: map[string]string{"FORCE_COLOR": ""}, expected: true}, + {name: "FORCE_COLOR=0", env: map[string]string{"FORCE_COLOR": "0"}, tty: true, expected: false}, + {name: "FORCE_COLOR=1", env: map[string]string{"FORCE_COLOR": "1"}, expected: true}, + {name: "FORCE_COLOR=2", env: map[string]string{"FORCE_COLOR": "2"}, expected: true}, + {name: "FORCE_COLOR=3", env: map[string]string{"FORCE_COLOR": "3"}, expected: true}, + {name: "FORCE_COLOR=4", env: map[string]string{"FORCE_COLOR": "4"}, tty: true, expected: false}, + {name: "FORCE_COLOR=true", env: map[string]string{"FORCE_COLOR": "true"}, expected: true}, + {name: "FORCE_COLOR=false", env: map[string]string{"FORCE_COLOR": "false"}, tty: true, expected: false}, + {name: "invalid FORCE_COLOR", env: map[string]string{"FORCE_COLOR": "invalid"}, tty: true, expected: false}, + { + name: "FORCE_COLOR overrides NO_COLOR", + env: map[string]string{"FORCE_COLOR": "1", "NO_COLOR": "1"}, + expected: true, + }, + { + name: "FORCE_COLOR overrides TERM=dumb", + env: map[string]string{"FORCE_COLOR": "1", "TERM": "dumb"}, + expected: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + sys := &colorTestSystem{ + timingTestSystem: &timingTestSystem{}, + env: test.env, + tty: test.tty, + } + if actual := defaultIsPretty(sys); actual != test.expected { + t.Errorf("defaultIsPretty() = %v, expected %v", actual, test.expected) + } + }) + } +} diff --git a/tsc/internal/execute/tsc/emit_test.go b/tsc/internal/execute/tsc/emit_test.go index 685b2817e062f..d2af648784948 100644 --- a/tsc/internal/execute/tsc/emit_test.go +++ b/tsc/internal/execute/tsc/emit_test.go @@ -32,6 +32,11 @@ func (s *contentMapperLoggingTestSystem) GetEnvironmentVariable(name string) str return "" } +func (s *contentMapperLoggingTestSystem) LookupEnvironmentVariable(name string) (string, bool) { + value := s.GetEnvironmentVariable(name) + return value, value != "" +} + func (s *contentMapperLoggingTestSystem) ErrorWriter() io.Writer { return &s.stderr } @@ -114,8 +119,11 @@ func (s *timingTestSystem) GetCurrentDirectory() string { return " func (s *timingTestSystem) WriteOutputIsTTY() bool { return false } func (s *timingTestSystem) GetWidthOfTerminal() int { return 0 } func (s *timingTestSystem) GetEnvironmentVariable(name string) string { return "" } -func (s *timingTestSystem) Now() time.Time { return s.clock.Now() } -func (s *timingTestSystem) SinceStart() time.Duration { return s.clock.SinceStart() } +func (s *timingTestSystem) LookupEnvironmentVariable(name string) (string, bool) { + return "", false +} +func (s *timingTestSystem) Now() time.Time { return s.clock.Now() } +func (s *timingTestSystem) SinceStart() time.Duration { return s.clock.SinceStart() } func (s *timingTestSystem) Spawn([]string, string, io.Writer) (io.ReadWriteCloser, error) { return nil, errors.New("spawn not implemented in timingTestSystem") diff --git a/tsc/internal/execute/tsctests/sys.go b/tsc/internal/execute/tsctests/sys.go index 0de0d22266408..971c98d4747b1 100644 --- a/tsc/internal/execute/tsctests/sys.go +++ b/tsc/internal/execute/tsctests/sys.go @@ -240,6 +240,11 @@ func (s *TestSys) GetEnvironmentVariable(name string) string { return s.env[name] } +func (s *TestSys) LookupEnvironmentVariable(name string) (string, bool) { + value, ok := s.env[name] + return value, ok +} + // Spawn serves the fake content mappers in-process, selecting the implementation by the exec command the // mapper package declares (see internal/testutil/contentmappertest), so tests exercise the full IPC stack // without spawning a subprocess. diff --git a/tsc/internal/execute/tsctests/tsc_test.go b/tsc/internal/execute/tsctests/tsc_test.go index 18ae19c0da396..b6d4b09e7f36f 100644 --- a/tsc/internal/execute/tsctests/tsc_test.go +++ b/tsc/internal/execute/tsctests/tsc_test.go @@ -41,7 +41,7 @@ func TestTscCommandline(t *testing.T) { commandLineArgs: nil, }, { - subScenario: "does not add color when NO_COLOR is set even if FORCE_COLOR is set", + subScenario: "FORCE_COLOR overrides NO_COLOR", env: map[string]string{ "NO_COLOR": "true", "FORCE_COLOR": "true", diff --git a/tsc/testdata/baselines/reference/tsc/commandLine/does-not-add-color-when-NO_COLOR-is-set-even-if-FORCE_COLOR-is-set.js b/tsc/testdata/baselines/reference/tsc/commandLine/FORCE_COLOR-overrides-NO_COLOR.js similarity index 84% rename from tsc/testdata/baselines/reference/tsc/commandLine/does-not-add-color-when-NO_COLOR-is-set-even-if-FORCE_COLOR-is-set.js rename to tsc/testdata/baselines/reference/tsc/commandLine/FORCE_COLOR-overrides-NO_COLOR.js index ae8e2b8a60729..f158bb77874f5 100644 --- a/tsc/testdata/baselines/reference/tsc/commandLine/does-not-add-color-when-NO_COLOR-is-set-even-if-FORCE_COLOR-is-set.js +++ b/tsc/testdata/baselines/reference/tsc/commandLine/FORCE_COLOR-overrides-NO_COLOR.js @@ -8,141 +8,141 @@ Output:: Version FakeTSVersion tsc: The TypeScript Compiler - Version FakeTSVersion -COMMON COMMANDS +COMMON COMMANDS - tsc + tsc Compiles the current project (tsconfig.json in the working directory.) - tsc app.ts util.ts + tsc app.ts util.ts Ignoring tsconfig.json, compiles the specified files with default compiler options. - tsc -b + tsc -b Build a composite project in the working directory. - tsc --init + tsc --init Creates a tsconfig.json with the recommended settings in the working directory. - tsc -p ./path/to/tsconfig.json + tsc -p ./path/to/tsconfig.json Compiles the TypeScript project located at the specified path. - tsc --help --all + tsc --help --all An expanded version of this information, showing all possible compiler options - tsc --noEmit - tsc --target esnext + tsc --noEmit + tsc --target esnext Compiles the current project, with additional settings. -COMMAND LINE FLAGS +COMMAND LINE FLAGS ---help, -h +--help, -h Print this message. ---watch, -w +--watch, -w Watch input files. ---all +--all Show all compiler options. ---version, -v +--version, -v Print the compiler's version. ---init +--init Initializes a TypeScript project and creates a tsconfig.json file. ---project, -p +--project, -p Compile the project given the path to its configuration file, or to a folder with a 'tsconfig.json'. ---showConfig +--showConfig Print the final configuration instead of building. ---ignoreConfig +--ignoreConfig Ignore the tsconfig found and build with commandline options and files. ---build, -b +--build, -b Build one or more projects and their dependencies, if out of date -COMMON COMPILER OPTIONS +COMMON COMPILER OPTIONS ---pretty +--pretty Enable color and formatting in TypeScript's output to make compiler errors easier to read. type: boolean default: true ---declaration, -d +--declaration, -d Generate .d.ts files from TypeScript and JavaScript files in your project. type: boolean default: `false`, unless `composite` is set ---declarationMap +--declarationMap Create sourcemaps for d.ts files. type: boolean default: false ---emitDeclarationOnly +--emitDeclarationOnly Only output d.ts files and not JavaScript files. type: boolean default: false ---sourceMap +--sourceMap Create source map files for emitted JavaScript files. type: boolean default: false ---noEmit +--noEmit Disable emitting files from a compilation. type: boolean default: false ---target, -t +--target, -t Set the JavaScript language version for emitted JavaScript and include compatible library declarations. one of: es6/es2015, es2016, es2017, es2018, es2019, es2020, es2021, es2022, es2023, es2024, es2025, esnext default: es2025 ---module, -m +--module, -m Specify what module code is generated. one of: commonjs, es6/es2015, es2020, es2022, esnext, node16, node18, node20, nodenext, preserve default: undefined ---lib +--lib Specify a set of bundled library declaration files that describe the target runtime environment. one or more: es5, es6/es2015, es7/es2016, es2017, es2018, es2019, es2020, es2021, es2022, es2023, es2024, es2025, esnext, dom, dom.iterable, dom.asynciterable, webworker, webworker.importscripts, webworker.iterable, webworker.asynciterable, scripthost, es2015.core, es2015.collection, es2015.generator, es2015.iterable, es2015.promise, es2015.proxy, es2015.reflect, es2015.symbol, es2015.symbol.wellknown, es2016.array.include, es2016.intl, es2017.arraybuffer, es2017.date, es2017.object, es2017.sharedmemory, es2017.string, es2017.intl, es2017.typedarrays, es2018.asyncgenerator, es2018.asynciterable/esnext.asynciterable, es2018.intl, es2018.promise, es2018.regexp, es2019.array, es2019.object, es2019.string, es2019.symbol/esnext.symbol, es2019.intl, es2020.bigint/esnext.bigint, es2020.date, es2020.promise, es2020.sharedmemory, es2020.string, es2020.symbol.wellknown, es2020.intl, es2020.number, es2021.promise, es2021.string, es2021.weakref/esnext.weakref, es2021.intl, es2022.array, es2022.error, es2022.intl, es2022.object, es2022.string, es2022.regexp, es2023.array, es2023.collection, es2023.intl, es2024.arraybuffer, es2024.collection, es2024.object/esnext.object, es2024.promise, es2024.regexp/esnext.regexp, es2024.sharedmemory, es2024.string/esnext.string, es2025.collection, es2025.float16/esnext.float16, es2025.intl, es2025.iterator/esnext.iterator, es2025.promise/esnext.promise, es2025.regexp, esnext.array, esnext.collection, esnext.date, esnext.decorators, esnext.disposable, esnext.error, esnext.intl, esnext.sharedmemory, esnext.temporal, esnext.typedarrays, decorators, decorators.legacy default: undefined ---allowJs +--allowJs Allow JavaScript files to be a part of your program. Use the 'checkJs' option to get errors from these files. type: boolean default: `false`, unless `checkJs` is set ---checkJs +--checkJs Enable error reporting in type-checked JavaScript files. type: boolean default: false ---jsx +--jsx Specify what JSX code is generated. one of: preserve, react-native, react-jsx, react-jsxdev, react default: undefined ---outFile +--outFile Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. ---outDir +--outDir Specify an output folder for all emitted files. ---removeComments +--removeComments Disable emitting comments. type: boolean default: false ---strict +--strict Enable all strict type-checking options. type: boolean default: true ---types +--types Specify type package names to be included without being referenced in a source file. ---esModuleInterop +--esModuleInterop Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. type: boolean default: true From 28be84b6ab3f64f124b4dfecc49dd25c6eb13897 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 20 Aug 2026 21:41:42 -0700 Subject: [PATCH 2/3] Test color environment handling through tsc --- tsc/internal/execute/tsc/diagnostics_test.go | 72 ------- tsc/internal/execute/tsctests/runner.go | 1 + tsc/internal/execute/tsctests/sys.go | 11 +- tsc/internal/execute/tsctests/tsc_test.go | 47 ++--- .../FORCE_COLOR-overrides-NO_COLOR.js | 181 ++++-------------- .../FORCE_COLOR-overrides-dumb-TERM.js | 41 ++++ ..._COLOR-is-empty-and-output-is-not-a-TTY.js | 41 ++++ ...CE_COLOR-is-one-and-output-is-not-a-TTY.js | 41 ++++ ..._COLOR-is-three-and-output-is-not-a-TTY.js | 41 ++++ ...E_COLOR-is-true-and-output-is-not-a-TTY.js | 41 ++++ ...CE_COLOR-is-two-and-output-is-not-a-TTY.js | 41 ++++ .../adds-color-when-NO_COLOR-is-empty.js | 41 ++++ ...not-add-color-when-FORCE_COLOR-is-false.js | 34 ++++ ...-not-add-color-when-FORCE_COLOR-is-four.js | 34 ++++ ...t-add-color-when-FORCE_COLOR-is-invalid.js | 34 ++++ ...-not-add-color-when-FORCE_COLOR-is-zero.js | 34 ++++ ...does-not-add-color-when-NO_COLOR-is-set.js | 174 +++-------------- .../does-not-add-color-when-TERM-is-dumb.js | 34 ++++ 18 files changed, 554 insertions(+), 389 deletions(-) delete mode 100644 tsc/internal/execute/tsc/diagnostics_test.go create mode 100644 tsc/testdata/baselines/reference/tsc/commandLine/FORCE_COLOR-overrides-dumb-TERM.js create mode 100644 tsc/testdata/baselines/reference/tsc/commandLine/adds-color-when-FORCE_COLOR-is-empty-and-output-is-not-a-TTY.js create mode 100644 tsc/testdata/baselines/reference/tsc/commandLine/adds-color-when-FORCE_COLOR-is-one-and-output-is-not-a-TTY.js create mode 100644 tsc/testdata/baselines/reference/tsc/commandLine/adds-color-when-FORCE_COLOR-is-three-and-output-is-not-a-TTY.js create mode 100644 tsc/testdata/baselines/reference/tsc/commandLine/adds-color-when-FORCE_COLOR-is-true-and-output-is-not-a-TTY.js create mode 100644 tsc/testdata/baselines/reference/tsc/commandLine/adds-color-when-FORCE_COLOR-is-two-and-output-is-not-a-TTY.js create mode 100644 tsc/testdata/baselines/reference/tsc/commandLine/adds-color-when-NO_COLOR-is-empty.js create mode 100644 tsc/testdata/baselines/reference/tsc/commandLine/does-not-add-color-when-FORCE_COLOR-is-false.js create mode 100644 tsc/testdata/baselines/reference/tsc/commandLine/does-not-add-color-when-FORCE_COLOR-is-four.js create mode 100644 tsc/testdata/baselines/reference/tsc/commandLine/does-not-add-color-when-FORCE_COLOR-is-invalid.js create mode 100644 tsc/testdata/baselines/reference/tsc/commandLine/does-not-add-color-when-FORCE_COLOR-is-zero.js create mode 100644 tsc/testdata/baselines/reference/tsc/commandLine/does-not-add-color-when-TERM-is-dumb.js diff --git a/tsc/internal/execute/tsc/diagnostics_test.go b/tsc/internal/execute/tsc/diagnostics_test.go deleted file mode 100644 index 95be81f4d4de5..0000000000000 --- a/tsc/internal/execute/tsc/diagnostics_test.go +++ /dev/null @@ -1,72 +0,0 @@ -package tsc - -import "testing" - -type colorTestSystem struct { - *timingTestSystem - env map[string]string - tty bool -} - -func (s *colorTestSystem) WriteOutputIsTTY() bool { - return s.tty -} - -func (s *colorTestSystem) GetEnvironmentVariable(name string) string { - return s.env[name] -} - -func (s *colorTestSystem) LookupEnvironmentVariable(name string) (string, bool) { - value, ok := s.env[name] - return value, ok -} - -func TestDefaultIsPretty(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - env map[string]string - tty bool - expected bool - }{ - {name: "TTY", tty: true, expected: true}, - {name: "non-TTY", tty: false, expected: false}, - {name: "NO_COLOR", env: map[string]string{"NO_COLOR": "1"}, tty: true, expected: false}, - {name: "empty NO_COLOR", env: map[string]string{"NO_COLOR": ""}, tty: true, expected: true}, - {name: "TERM=dumb", env: map[string]string{"TERM": "dumb"}, tty: true, expected: false}, - {name: "empty FORCE_COLOR", env: map[string]string{"FORCE_COLOR": ""}, expected: true}, - {name: "FORCE_COLOR=0", env: map[string]string{"FORCE_COLOR": "0"}, tty: true, expected: false}, - {name: "FORCE_COLOR=1", env: map[string]string{"FORCE_COLOR": "1"}, expected: true}, - {name: "FORCE_COLOR=2", env: map[string]string{"FORCE_COLOR": "2"}, expected: true}, - {name: "FORCE_COLOR=3", env: map[string]string{"FORCE_COLOR": "3"}, expected: true}, - {name: "FORCE_COLOR=4", env: map[string]string{"FORCE_COLOR": "4"}, tty: true, expected: false}, - {name: "FORCE_COLOR=true", env: map[string]string{"FORCE_COLOR": "true"}, expected: true}, - {name: "FORCE_COLOR=false", env: map[string]string{"FORCE_COLOR": "false"}, tty: true, expected: false}, - {name: "invalid FORCE_COLOR", env: map[string]string{"FORCE_COLOR": "invalid"}, tty: true, expected: false}, - { - name: "FORCE_COLOR overrides NO_COLOR", - env: map[string]string{"FORCE_COLOR": "1", "NO_COLOR": "1"}, - expected: true, - }, - { - name: "FORCE_COLOR overrides TERM=dumb", - env: map[string]string{"FORCE_COLOR": "1", "TERM": "dumb"}, - expected: true, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - sys := &colorTestSystem{ - timingTestSystem: &timingTestSystem{}, - env: test.env, - tty: test.tty, - } - if actual := defaultIsPretty(sys); actual != test.expected { - t.Errorf("defaultIsPretty() = %v, expected %v", actual, test.expected) - } - }) - } -} diff --git a/tsc/internal/execute/tsctests/runner.go b/tsc/internal/execute/tsctests/runner.go index f7341136aaf93..60b41a947e1e8 100644 --- a/tsc/internal/execute/tsctests/runner.go +++ b/tsc/internal/execute/tsctests/runner.go @@ -37,6 +37,7 @@ type tscInput struct { cwd string edits []*tscEdit env map[string]string + outputIsTTY *bool ignoreCase bool windowsStyleRoot string } diff --git a/tsc/internal/execute/tsctests/sys.go b/tsc/internal/execute/tsctests/sys.go index 971c98d4747b1..03fca0eaaedb1 100644 --- a/tsc/internal/execute/tsctests/sys.go +++ b/tsc/internal/execute/tsctests/sys.go @@ -96,8 +96,9 @@ func NewTscSystem(files FileMap, useCaseSensitiveFileNames bool, cwd string) *Te fs: &testFs{ FS: vfstest.FromMapWithClock(files, useCaseSensitiveFileNames, clock), }, - cwd: cwd, - clock: clock, + cwd: cwd, + outputIsTTY: true, + clock: clock, } } @@ -128,6 +129,9 @@ func newTestSys(tscInput *tscInput, forIncrementalCorrectness bool) *TestSys { sys := NewTscSystem(tscInput.files, !tscInput.ignoreCase, cwd) sys.defaultLibraryPath = libPath sys.currentWrite = currentWrite + if tscInput.outputIsTTY != nil { + sys.outputIsTTY = *tscInput.outputIsTTY + } sys.tracer = harnessutil.NewTracerForBaselining(tspath.ComparePathsOptions{ UseCaseSensitiveFileNames: !tscInput.ignoreCase, CurrentDirectory: cwd, @@ -167,6 +171,7 @@ type TestSys struct { defaultLibraryPath string cwd string env map[string]string + outputIsTTY bool clock *TestClock } @@ -226,7 +231,7 @@ func (s *TestSys) ErrorWriter() io.Writer { } func (s *TestSys) WriteOutputIsTTY() bool { - return true + return s.outputIsTTY } func (s *TestSys) GetWidthOfTerminal() int { diff --git a/tsc/internal/execute/tsctests/tsc_test.go b/tsc/internal/execute/tsctests/tsc_test.go index b6d4b09e7f36f..43e4949f3ba3c 100644 --- a/tsc/internal/execute/tsctests/tsc_test.go +++ b/tsc/internal/execute/tsctests/tsc_test.go @@ -14,6 +14,17 @@ import ( func TestTscCommandline(t *testing.T) { t.Parallel() + colorTest := func(subScenario string, env map[string]string, outputIsTTY bool) *tscInput { + return &tscInput{ + subScenario: subScenario, + files: FileMap{ + "/home/src/workspaces/project/index.ts": "const x: string = 1;", + }, + commandLineArgs: []string{"index.ts", "--noEmit"}, + env: env, + outputIsTTY: new(outputIsTTY), + } + } testCases := []*tscInput{ { subScenario: "show help with ExitStatus.DiagnosticsPresent_OutputsSkipped", @@ -26,28 +37,20 @@ func TestTscCommandline(t *testing.T) { subScenario: "show help with ExitStatus.DiagnosticsPresent_OutputsSkipped when host cannot provide terminal width", commandLineArgs: nil, }, - { - subScenario: "does not add color when NO_COLOR is set", - env: map[string]string{ - "NO_COLOR": "true", - }, - commandLineArgs: nil, - }, - { - subScenario: "adds color when FORCE_COLOR is set", - env: map[string]string{ - "FORCE_COLOR": "true", - }, - commandLineArgs: nil, - }, - { - subScenario: "FORCE_COLOR overrides NO_COLOR", - env: map[string]string{ - "NO_COLOR": "true", - "FORCE_COLOR": "true", - }, - commandLineArgs: nil, - }, + colorTest("does not add color when NO_COLOR is set", map[string]string{"NO_COLOR": "true"}, true), + colorTest("adds color when NO_COLOR is empty", map[string]string{"NO_COLOR": ""}, true), + colorTest("adds color when FORCE_COLOR is empty and output is not a TTY", map[string]string{"FORCE_COLOR": ""}, false), + colorTest("does not add color when FORCE_COLOR is zero", map[string]string{"FORCE_COLOR": "0"}, true), + colorTest("adds color when FORCE_COLOR is one and output is not a TTY", map[string]string{"FORCE_COLOR": "1"}, false), + colorTest("adds color when FORCE_COLOR is two and output is not a TTY", map[string]string{"FORCE_COLOR": "2"}, false), + colorTest("adds color when FORCE_COLOR is three and output is not a TTY", map[string]string{"FORCE_COLOR": "3"}, false), + colorTest("does not add color when FORCE_COLOR is four", map[string]string{"FORCE_COLOR": "4"}, true), + colorTest("adds color when FORCE_COLOR is true and output is not a TTY", map[string]string{"FORCE_COLOR": "true"}, false), + colorTest("does not add color when FORCE_COLOR is false", map[string]string{"FORCE_COLOR": "false"}, true), + colorTest("does not add color when FORCE_COLOR is invalid", map[string]string{"FORCE_COLOR": "invalid"}, true), + colorTest("FORCE_COLOR overrides NO_COLOR", map[string]string{"NO_COLOR": "true", "FORCE_COLOR": "true"}, false), + colorTest("does not add color when TERM is dumb", map[string]string{"TERM": "dumb"}, true), + colorTest("FORCE_COLOR overrides dumb TERM", map[string]string{"TERM": "dumb", "FORCE_COLOR": "true"}, false), { subScenario: "when build not first argument", commandLineArgs: []string{"--verbose", "--build"}, diff --git a/tsc/testdata/baselines/reference/tsc/commandLine/FORCE_COLOR-overrides-NO_COLOR.js b/tsc/testdata/baselines/reference/tsc/commandLine/FORCE_COLOR-overrides-NO_COLOR.js index f158bb77874f5..45a8e17f9b38d 100644 --- a/tsc/testdata/baselines/reference/tsc/commandLine/FORCE_COLOR-overrides-NO_COLOR.js +++ b/tsc/testdata/baselines/reference/tsc/commandLine/FORCE_COLOR-overrides-NO_COLOR.js @@ -1,152 +1,41 @@ currentDirectory::/home/src/workspaces/project useCaseSensitiveFileNames::true Input:: +//// [/home/src/workspaces/project/index.ts] *new* +const x: string = 1; -tsgo -ExitStatus:: DiagnosticsPresent_OutputsSkipped +tsgo index.ts --noEmit +ExitStatus:: DiagnosticsPresent_OutputsGenerated Output:: -Version FakeTSVersion -tsc: The TypeScript Compiler - Version FakeTSVersion - -COMMON COMMANDS - - tsc - Compiles the current project (tsconfig.json in the working directory.) - - tsc app.ts util.ts - Ignoring tsconfig.json, compiles the specified files with default compiler options. - - tsc -b - Build a composite project in the working directory. - - tsc --init - Creates a tsconfig.json with the recommended settings in the working directory. - - tsc -p ./path/to/tsconfig.json - Compiles the TypeScript project located at the specified path. - - tsc --help --all - An expanded version of this information, showing all possible compiler options - - tsc --noEmit - tsc --target esnext - Compiles the current project, with additional settings. - -COMMAND LINE FLAGS - ---help, -h -Print this message. - ---watch, -w -Watch input files. - ---all -Show all compiler options. - ---version, -v -Print the compiler's version. - ---init -Initializes a TypeScript project and creates a tsconfig.json file. - ---project, -p -Compile the project given the path to its configuration file, or to a folder with a 'tsconfig.json'. - ---showConfig -Print the final configuration instead of building. - ---ignoreConfig -Ignore the tsconfig found and build with commandline options and files. - ---build, -b -Build one or more projects and their dependencies, if out of date - -COMMON COMPILER OPTIONS - ---pretty -Enable color and formatting in TypeScript's output to make compiler errors easier to read. -type: boolean -default: true - ---declaration, -d -Generate .d.ts files from TypeScript and JavaScript files in your project. -type: boolean -default: `false`, unless `composite` is set - ---declarationMap -Create sourcemaps for d.ts files. -type: boolean -default: false - ---emitDeclarationOnly -Only output d.ts files and not JavaScript files. -type: boolean -default: false - ---sourceMap -Create source map files for emitted JavaScript files. -type: boolean -default: false - ---noEmit -Disable emitting files from a compilation. -type: boolean -default: false - ---target, -t -Set the JavaScript language version for emitted JavaScript and include compatible library declarations. -one of: es6/es2015, es2016, es2017, es2018, es2019, es2020, es2021, es2022, es2023, es2024, es2025, esnext -default: es2025 - ---module, -m -Specify what module code is generated. -one of: commonjs, es6/es2015, es2020, es2022, esnext, node16, node18, node20, nodenext, preserve -default: undefined - ---lib -Specify a set of bundled library declaration files that describe the target runtime environment. -one or more: es5, es6/es2015, es7/es2016, es2017, es2018, es2019, es2020, es2021, es2022, es2023, es2024, es2025, esnext, dom, dom.iterable, dom.asynciterable, webworker, webworker.importscripts, webworker.iterable, webworker.asynciterable, scripthost, es2015.core, es2015.collection, es2015.generator, es2015.iterable, es2015.promise, es2015.proxy, es2015.reflect, es2015.symbol, es2015.symbol.wellknown, es2016.array.include, es2016.intl, es2017.arraybuffer, es2017.date, es2017.object, es2017.sharedmemory, es2017.string, es2017.intl, es2017.typedarrays, es2018.asyncgenerator, es2018.asynciterable/esnext.asynciterable, es2018.intl, es2018.promise, es2018.regexp, es2019.array, es2019.object, es2019.string, es2019.symbol/esnext.symbol, es2019.intl, es2020.bigint/esnext.bigint, es2020.date, es2020.promise, es2020.sharedmemory, es2020.string, es2020.symbol.wellknown, es2020.intl, es2020.number, es2021.promise, es2021.string, es2021.weakref/esnext.weakref, es2021.intl, es2022.array, es2022.error, es2022.intl, es2022.object, es2022.string, es2022.regexp, es2023.array, es2023.collection, es2023.intl, es2024.arraybuffer, es2024.collection, es2024.object/esnext.object, es2024.promise, es2024.regexp/esnext.regexp, es2024.sharedmemory, es2024.string/esnext.string, es2025.collection, es2025.float16/esnext.float16, es2025.intl, es2025.iterator/esnext.iterator, es2025.promise/esnext.promise, es2025.regexp, esnext.array, esnext.collection, esnext.date, esnext.decorators, esnext.disposable, esnext.error, esnext.intl, esnext.sharedmemory, esnext.temporal, esnext.typedarrays, decorators, decorators.legacy -default: undefined - ---allowJs -Allow JavaScript files to be a part of your program. Use the 'checkJs' option to get errors from these files. -type: boolean -default: `false`, unless `checkJs` is set - ---checkJs -Enable error reporting in type-checked JavaScript files. -type: boolean -default: false - ---jsx -Specify what JSX code is generated. -one of: preserve, react-native, react-jsx, react-jsxdev, react -default: undefined - ---outFile -Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. - ---outDir -Specify an output folder for all emitted files. - ---removeComments -Disable emitting comments. -type: boolean -default: false - ---strict -Enable all strict type-checking options. -type: boolean -default: true - ---types -Specify type package names to be included without being referenced in a source file. - ---esModuleInterop -Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. -type: boolean -default: true - -You can learn about all of the compiler options at https://aka.ms/tsc - +index.ts:1:7 - error TS2322: Type 'number' is not assignable to type 'string'. + +1 const x: string = 1; +   ~ + + +Found 1 error in index.ts:1 + +//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib* +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +interface SymbolConstructor { + (desc?: string | number): symbol; + for(name: string): symbol; + readonly toStringTag: symbol; +} +declare var Symbol: SymbolConstructor; +interface Symbol { + readonly [Symbol.toStringTag]: string; +} +declare const console: { log(msg: any): void; }; diff --git a/tsc/testdata/baselines/reference/tsc/commandLine/FORCE_COLOR-overrides-dumb-TERM.js b/tsc/testdata/baselines/reference/tsc/commandLine/FORCE_COLOR-overrides-dumb-TERM.js new file mode 100644 index 0000000000000..45a8e17f9b38d --- /dev/null +++ b/tsc/testdata/baselines/reference/tsc/commandLine/FORCE_COLOR-overrides-dumb-TERM.js @@ -0,0 +1,41 @@ +currentDirectory::/home/src/workspaces/project +useCaseSensitiveFileNames::true +Input:: +//// [/home/src/workspaces/project/index.ts] *new* +const x: string = 1; + +tsgo index.ts --noEmit +ExitStatus:: DiagnosticsPresent_OutputsGenerated +Output:: +index.ts:1:7 - error TS2322: Type 'number' is not assignable to type 'string'. + +1 const x: string = 1; +   ~ + + +Found 1 error in index.ts:1 + +//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib* +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +interface SymbolConstructor { + (desc?: string | number): symbol; + for(name: string): symbol; + readonly toStringTag: symbol; +} +declare var Symbol: SymbolConstructor; +interface Symbol { + readonly [Symbol.toStringTag]: string; +} +declare const console: { log(msg: any): void; }; + diff --git a/tsc/testdata/baselines/reference/tsc/commandLine/adds-color-when-FORCE_COLOR-is-empty-and-output-is-not-a-TTY.js b/tsc/testdata/baselines/reference/tsc/commandLine/adds-color-when-FORCE_COLOR-is-empty-and-output-is-not-a-TTY.js new file mode 100644 index 0000000000000..45a8e17f9b38d --- /dev/null +++ b/tsc/testdata/baselines/reference/tsc/commandLine/adds-color-when-FORCE_COLOR-is-empty-and-output-is-not-a-TTY.js @@ -0,0 +1,41 @@ +currentDirectory::/home/src/workspaces/project +useCaseSensitiveFileNames::true +Input:: +//// [/home/src/workspaces/project/index.ts] *new* +const x: string = 1; + +tsgo index.ts --noEmit +ExitStatus:: DiagnosticsPresent_OutputsGenerated +Output:: +index.ts:1:7 - error TS2322: Type 'number' is not assignable to type 'string'. + +1 const x: string = 1; +   ~ + + +Found 1 error in index.ts:1 + +//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib* +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +interface SymbolConstructor { + (desc?: string | number): symbol; + for(name: string): symbol; + readonly toStringTag: symbol; +} +declare var Symbol: SymbolConstructor; +interface Symbol { + readonly [Symbol.toStringTag]: string; +} +declare const console: { log(msg: any): void; }; + diff --git a/tsc/testdata/baselines/reference/tsc/commandLine/adds-color-when-FORCE_COLOR-is-one-and-output-is-not-a-TTY.js b/tsc/testdata/baselines/reference/tsc/commandLine/adds-color-when-FORCE_COLOR-is-one-and-output-is-not-a-TTY.js new file mode 100644 index 0000000000000..45a8e17f9b38d --- /dev/null +++ b/tsc/testdata/baselines/reference/tsc/commandLine/adds-color-when-FORCE_COLOR-is-one-and-output-is-not-a-TTY.js @@ -0,0 +1,41 @@ +currentDirectory::/home/src/workspaces/project +useCaseSensitiveFileNames::true +Input:: +//// [/home/src/workspaces/project/index.ts] *new* +const x: string = 1; + +tsgo index.ts --noEmit +ExitStatus:: DiagnosticsPresent_OutputsGenerated +Output:: +index.ts:1:7 - error TS2322: Type 'number' is not assignable to type 'string'. + +1 const x: string = 1; +   ~ + + +Found 1 error in index.ts:1 + +//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib* +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +interface SymbolConstructor { + (desc?: string | number): symbol; + for(name: string): symbol; + readonly toStringTag: symbol; +} +declare var Symbol: SymbolConstructor; +interface Symbol { + readonly [Symbol.toStringTag]: string; +} +declare const console: { log(msg: any): void; }; + diff --git a/tsc/testdata/baselines/reference/tsc/commandLine/adds-color-when-FORCE_COLOR-is-three-and-output-is-not-a-TTY.js b/tsc/testdata/baselines/reference/tsc/commandLine/adds-color-when-FORCE_COLOR-is-three-and-output-is-not-a-TTY.js new file mode 100644 index 0000000000000..45a8e17f9b38d --- /dev/null +++ b/tsc/testdata/baselines/reference/tsc/commandLine/adds-color-when-FORCE_COLOR-is-three-and-output-is-not-a-TTY.js @@ -0,0 +1,41 @@ +currentDirectory::/home/src/workspaces/project +useCaseSensitiveFileNames::true +Input:: +//// [/home/src/workspaces/project/index.ts] *new* +const x: string = 1; + +tsgo index.ts --noEmit +ExitStatus:: DiagnosticsPresent_OutputsGenerated +Output:: +index.ts:1:7 - error TS2322: Type 'number' is not assignable to type 'string'. + +1 const x: string = 1; +   ~ + + +Found 1 error in index.ts:1 + +//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib* +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +interface SymbolConstructor { + (desc?: string | number): symbol; + for(name: string): symbol; + readonly toStringTag: symbol; +} +declare var Symbol: SymbolConstructor; +interface Symbol { + readonly [Symbol.toStringTag]: string; +} +declare const console: { log(msg: any): void; }; + diff --git a/tsc/testdata/baselines/reference/tsc/commandLine/adds-color-when-FORCE_COLOR-is-true-and-output-is-not-a-TTY.js b/tsc/testdata/baselines/reference/tsc/commandLine/adds-color-when-FORCE_COLOR-is-true-and-output-is-not-a-TTY.js new file mode 100644 index 0000000000000..45a8e17f9b38d --- /dev/null +++ b/tsc/testdata/baselines/reference/tsc/commandLine/adds-color-when-FORCE_COLOR-is-true-and-output-is-not-a-TTY.js @@ -0,0 +1,41 @@ +currentDirectory::/home/src/workspaces/project +useCaseSensitiveFileNames::true +Input:: +//// [/home/src/workspaces/project/index.ts] *new* +const x: string = 1; + +tsgo index.ts --noEmit +ExitStatus:: DiagnosticsPresent_OutputsGenerated +Output:: +index.ts:1:7 - error TS2322: Type 'number' is not assignable to type 'string'. + +1 const x: string = 1; +   ~ + + +Found 1 error in index.ts:1 + +//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib* +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +interface SymbolConstructor { + (desc?: string | number): symbol; + for(name: string): symbol; + readonly toStringTag: symbol; +} +declare var Symbol: SymbolConstructor; +interface Symbol { + readonly [Symbol.toStringTag]: string; +} +declare const console: { log(msg: any): void; }; + diff --git a/tsc/testdata/baselines/reference/tsc/commandLine/adds-color-when-FORCE_COLOR-is-two-and-output-is-not-a-TTY.js b/tsc/testdata/baselines/reference/tsc/commandLine/adds-color-when-FORCE_COLOR-is-two-and-output-is-not-a-TTY.js new file mode 100644 index 0000000000000..45a8e17f9b38d --- /dev/null +++ b/tsc/testdata/baselines/reference/tsc/commandLine/adds-color-when-FORCE_COLOR-is-two-and-output-is-not-a-TTY.js @@ -0,0 +1,41 @@ +currentDirectory::/home/src/workspaces/project +useCaseSensitiveFileNames::true +Input:: +//// [/home/src/workspaces/project/index.ts] *new* +const x: string = 1; + +tsgo index.ts --noEmit +ExitStatus:: DiagnosticsPresent_OutputsGenerated +Output:: +index.ts:1:7 - error TS2322: Type 'number' is not assignable to type 'string'. + +1 const x: string = 1; +   ~ + + +Found 1 error in index.ts:1 + +//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib* +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +interface SymbolConstructor { + (desc?: string | number): symbol; + for(name: string): symbol; + readonly toStringTag: symbol; +} +declare var Symbol: SymbolConstructor; +interface Symbol { + readonly [Symbol.toStringTag]: string; +} +declare const console: { log(msg: any): void; }; + diff --git a/tsc/testdata/baselines/reference/tsc/commandLine/adds-color-when-NO_COLOR-is-empty.js b/tsc/testdata/baselines/reference/tsc/commandLine/adds-color-when-NO_COLOR-is-empty.js new file mode 100644 index 0000000000000..45a8e17f9b38d --- /dev/null +++ b/tsc/testdata/baselines/reference/tsc/commandLine/adds-color-when-NO_COLOR-is-empty.js @@ -0,0 +1,41 @@ +currentDirectory::/home/src/workspaces/project +useCaseSensitiveFileNames::true +Input:: +//// [/home/src/workspaces/project/index.ts] *new* +const x: string = 1; + +tsgo index.ts --noEmit +ExitStatus:: DiagnosticsPresent_OutputsGenerated +Output:: +index.ts:1:7 - error TS2322: Type 'number' is not assignable to type 'string'. + +1 const x: string = 1; +   ~ + + +Found 1 error in index.ts:1 + +//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib* +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +interface SymbolConstructor { + (desc?: string | number): symbol; + for(name: string): symbol; + readonly toStringTag: symbol; +} +declare var Symbol: SymbolConstructor; +interface Symbol { + readonly [Symbol.toStringTag]: string; +} +declare const console: { log(msg: any): void; }; + diff --git a/tsc/testdata/baselines/reference/tsc/commandLine/does-not-add-color-when-FORCE_COLOR-is-false.js b/tsc/testdata/baselines/reference/tsc/commandLine/does-not-add-color-when-FORCE_COLOR-is-false.js new file mode 100644 index 0000000000000..0c4f970e61a20 --- /dev/null +++ b/tsc/testdata/baselines/reference/tsc/commandLine/does-not-add-color-when-FORCE_COLOR-is-false.js @@ -0,0 +1,34 @@ +currentDirectory::/home/src/workspaces/project +useCaseSensitiveFileNames::true +Input:: +//// [/home/src/workspaces/project/index.ts] *new* +const x: string = 1; + +tsgo index.ts --noEmit +ExitStatus:: DiagnosticsPresent_OutputsGenerated +Output:: +index.ts(1,7): error TS2322: Type 'number' is not assignable to type 'string'. +//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib* +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +interface SymbolConstructor { + (desc?: string | number): symbol; + for(name: string): symbol; + readonly toStringTag: symbol; +} +declare var Symbol: SymbolConstructor; +interface Symbol { + readonly [Symbol.toStringTag]: string; +} +declare const console: { log(msg: any): void; }; + diff --git a/tsc/testdata/baselines/reference/tsc/commandLine/does-not-add-color-when-FORCE_COLOR-is-four.js b/tsc/testdata/baselines/reference/tsc/commandLine/does-not-add-color-when-FORCE_COLOR-is-four.js new file mode 100644 index 0000000000000..0c4f970e61a20 --- /dev/null +++ b/tsc/testdata/baselines/reference/tsc/commandLine/does-not-add-color-when-FORCE_COLOR-is-four.js @@ -0,0 +1,34 @@ +currentDirectory::/home/src/workspaces/project +useCaseSensitiveFileNames::true +Input:: +//// [/home/src/workspaces/project/index.ts] *new* +const x: string = 1; + +tsgo index.ts --noEmit +ExitStatus:: DiagnosticsPresent_OutputsGenerated +Output:: +index.ts(1,7): error TS2322: Type 'number' is not assignable to type 'string'. +//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib* +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +interface SymbolConstructor { + (desc?: string | number): symbol; + for(name: string): symbol; + readonly toStringTag: symbol; +} +declare var Symbol: SymbolConstructor; +interface Symbol { + readonly [Symbol.toStringTag]: string; +} +declare const console: { log(msg: any): void; }; + diff --git a/tsc/testdata/baselines/reference/tsc/commandLine/does-not-add-color-when-FORCE_COLOR-is-invalid.js b/tsc/testdata/baselines/reference/tsc/commandLine/does-not-add-color-when-FORCE_COLOR-is-invalid.js new file mode 100644 index 0000000000000..0c4f970e61a20 --- /dev/null +++ b/tsc/testdata/baselines/reference/tsc/commandLine/does-not-add-color-when-FORCE_COLOR-is-invalid.js @@ -0,0 +1,34 @@ +currentDirectory::/home/src/workspaces/project +useCaseSensitiveFileNames::true +Input:: +//// [/home/src/workspaces/project/index.ts] *new* +const x: string = 1; + +tsgo index.ts --noEmit +ExitStatus:: DiagnosticsPresent_OutputsGenerated +Output:: +index.ts(1,7): error TS2322: Type 'number' is not assignable to type 'string'. +//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib* +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +interface SymbolConstructor { + (desc?: string | number): symbol; + for(name: string): symbol; + readonly toStringTag: symbol; +} +declare var Symbol: SymbolConstructor; +interface Symbol { + readonly [Symbol.toStringTag]: string; +} +declare const console: { log(msg: any): void; }; + diff --git a/tsc/testdata/baselines/reference/tsc/commandLine/does-not-add-color-when-FORCE_COLOR-is-zero.js b/tsc/testdata/baselines/reference/tsc/commandLine/does-not-add-color-when-FORCE_COLOR-is-zero.js new file mode 100644 index 0000000000000..0c4f970e61a20 --- /dev/null +++ b/tsc/testdata/baselines/reference/tsc/commandLine/does-not-add-color-when-FORCE_COLOR-is-zero.js @@ -0,0 +1,34 @@ +currentDirectory::/home/src/workspaces/project +useCaseSensitiveFileNames::true +Input:: +//// [/home/src/workspaces/project/index.ts] *new* +const x: string = 1; + +tsgo index.ts --noEmit +ExitStatus:: DiagnosticsPresent_OutputsGenerated +Output:: +index.ts(1,7): error TS2322: Type 'number' is not assignable to type 'string'. +//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib* +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +interface SymbolConstructor { + (desc?: string | number): symbol; + for(name: string): symbol; + readonly toStringTag: symbol; +} +declare var Symbol: SymbolConstructor; +interface Symbol { + readonly [Symbol.toStringTag]: string; +} +declare const console: { log(msg: any): void; }; + diff --git a/tsc/testdata/baselines/reference/tsc/commandLine/does-not-add-color-when-NO_COLOR-is-set.js b/tsc/testdata/baselines/reference/tsc/commandLine/does-not-add-color-when-NO_COLOR-is-set.js index ae8e2b8a60729..0c4f970e61a20 100644 --- a/tsc/testdata/baselines/reference/tsc/commandLine/does-not-add-color-when-NO_COLOR-is-set.js +++ b/tsc/testdata/baselines/reference/tsc/commandLine/does-not-add-color-when-NO_COLOR-is-set.js @@ -1,152 +1,34 @@ currentDirectory::/home/src/workspaces/project useCaseSensitiveFileNames::true Input:: +//// [/home/src/workspaces/project/index.ts] *new* +const x: string = 1; -tsgo -ExitStatus:: DiagnosticsPresent_OutputsSkipped +tsgo index.ts --noEmit +ExitStatus:: DiagnosticsPresent_OutputsGenerated Output:: -Version FakeTSVersion -tsc: The TypeScript Compiler - Version FakeTSVersion - -COMMON COMMANDS - - tsc - Compiles the current project (tsconfig.json in the working directory.) - - tsc app.ts util.ts - Ignoring tsconfig.json, compiles the specified files with default compiler options. - - tsc -b - Build a composite project in the working directory. - - tsc --init - Creates a tsconfig.json with the recommended settings in the working directory. - - tsc -p ./path/to/tsconfig.json - Compiles the TypeScript project located at the specified path. - - tsc --help --all - An expanded version of this information, showing all possible compiler options - - tsc --noEmit - tsc --target esnext - Compiles the current project, with additional settings. - -COMMAND LINE FLAGS - ---help, -h -Print this message. - ---watch, -w -Watch input files. - ---all -Show all compiler options. - ---version, -v -Print the compiler's version. - ---init -Initializes a TypeScript project and creates a tsconfig.json file. - ---project, -p -Compile the project given the path to its configuration file, or to a folder with a 'tsconfig.json'. - ---showConfig -Print the final configuration instead of building. - ---ignoreConfig -Ignore the tsconfig found and build with commandline options and files. - ---build, -b -Build one or more projects and their dependencies, if out of date - -COMMON COMPILER OPTIONS - ---pretty -Enable color and formatting in TypeScript's output to make compiler errors easier to read. -type: boolean -default: true - ---declaration, -d -Generate .d.ts files from TypeScript and JavaScript files in your project. -type: boolean -default: `false`, unless `composite` is set - ---declarationMap -Create sourcemaps for d.ts files. -type: boolean -default: false - ---emitDeclarationOnly -Only output d.ts files and not JavaScript files. -type: boolean -default: false - ---sourceMap -Create source map files for emitted JavaScript files. -type: boolean -default: false - ---noEmit -Disable emitting files from a compilation. -type: boolean -default: false - ---target, -t -Set the JavaScript language version for emitted JavaScript and include compatible library declarations. -one of: es6/es2015, es2016, es2017, es2018, es2019, es2020, es2021, es2022, es2023, es2024, es2025, esnext -default: es2025 - ---module, -m -Specify what module code is generated. -one of: commonjs, es6/es2015, es2020, es2022, esnext, node16, node18, node20, nodenext, preserve -default: undefined - ---lib -Specify a set of bundled library declaration files that describe the target runtime environment. -one or more: es5, es6/es2015, es7/es2016, es2017, es2018, es2019, es2020, es2021, es2022, es2023, es2024, es2025, esnext, dom, dom.iterable, dom.asynciterable, webworker, webworker.importscripts, webworker.iterable, webworker.asynciterable, scripthost, es2015.core, es2015.collection, es2015.generator, es2015.iterable, es2015.promise, es2015.proxy, es2015.reflect, es2015.symbol, es2015.symbol.wellknown, es2016.array.include, es2016.intl, es2017.arraybuffer, es2017.date, es2017.object, es2017.sharedmemory, es2017.string, es2017.intl, es2017.typedarrays, es2018.asyncgenerator, es2018.asynciterable/esnext.asynciterable, es2018.intl, es2018.promise, es2018.regexp, es2019.array, es2019.object, es2019.string, es2019.symbol/esnext.symbol, es2019.intl, es2020.bigint/esnext.bigint, es2020.date, es2020.promise, es2020.sharedmemory, es2020.string, es2020.symbol.wellknown, es2020.intl, es2020.number, es2021.promise, es2021.string, es2021.weakref/esnext.weakref, es2021.intl, es2022.array, es2022.error, es2022.intl, es2022.object, es2022.string, es2022.regexp, es2023.array, es2023.collection, es2023.intl, es2024.arraybuffer, es2024.collection, es2024.object/esnext.object, es2024.promise, es2024.regexp/esnext.regexp, es2024.sharedmemory, es2024.string/esnext.string, es2025.collection, es2025.float16/esnext.float16, es2025.intl, es2025.iterator/esnext.iterator, es2025.promise/esnext.promise, es2025.regexp, esnext.array, esnext.collection, esnext.date, esnext.decorators, esnext.disposable, esnext.error, esnext.intl, esnext.sharedmemory, esnext.temporal, esnext.typedarrays, decorators, decorators.legacy -default: undefined - ---allowJs -Allow JavaScript files to be a part of your program. Use the 'checkJs' option to get errors from these files. -type: boolean -default: `false`, unless `checkJs` is set - ---checkJs -Enable error reporting in type-checked JavaScript files. -type: boolean -default: false - ---jsx -Specify what JSX code is generated. -one of: preserve, react-native, react-jsx, react-jsxdev, react -default: undefined - ---outFile -Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. - ---outDir -Specify an output folder for all emitted files. - ---removeComments -Disable emitting comments. -type: boolean -default: false - ---strict -Enable all strict type-checking options. -type: boolean -default: true - ---types -Specify type package names to be included without being referenced in a source file. - ---esModuleInterop -Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. -type: boolean -default: true - -You can learn about all of the compiler options at https://aka.ms/tsc - +index.ts(1,7): error TS2322: Type 'number' is not assignable to type 'string'. +//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib* +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +interface SymbolConstructor { + (desc?: string | number): symbol; + for(name: string): symbol; + readonly toStringTag: symbol; +} +declare var Symbol: SymbolConstructor; +interface Symbol { + readonly [Symbol.toStringTag]: string; +} +declare const console: { log(msg: any): void; }; diff --git a/tsc/testdata/baselines/reference/tsc/commandLine/does-not-add-color-when-TERM-is-dumb.js b/tsc/testdata/baselines/reference/tsc/commandLine/does-not-add-color-when-TERM-is-dumb.js new file mode 100644 index 0000000000000..0c4f970e61a20 --- /dev/null +++ b/tsc/testdata/baselines/reference/tsc/commandLine/does-not-add-color-when-TERM-is-dumb.js @@ -0,0 +1,34 @@ +currentDirectory::/home/src/workspaces/project +useCaseSensitiveFileNames::true +Input:: +//// [/home/src/workspaces/project/index.ts] *new* +const x: string = 1; + +tsgo index.ts --noEmit +ExitStatus:: DiagnosticsPresent_OutputsGenerated +Output:: +index.ts(1,7): error TS2322: Type 'number' is not assignable to type 'string'. +//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib* +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +interface SymbolConstructor { + (desc?: string | number): symbol; + for(name: string): symbol; + readonly toStringTag: symbol; +} +declare var Symbol: SymbolConstructor; +interface Symbol { + readonly [Symbol.toStringTag]: string; +} +declare const console: { log(msg: any): void; }; + From bfe13d85a426f0befc9595644aba3e994ca098a8 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 20 Aug 2026 21:46:22 -0700 Subject: [PATCH 3/3] Use two-result environment lookups --- tsc/cmd/tsc/sys.go | 6 +---- tsc/internal/execute/build/orchestrator.go | 2 +- tsc/internal/execute/tsc/compile.go | 5 ++-- tsc/internal/execute/tsc/diagnostics.go | 21 ++++++++-------- tsc/internal/execute/tsc/emit_test.go | 28 +++++++++------------- tsc/internal/execute/tsctests/sys.go | 8 ++----- tsc/internal/execute/watcher.go | 2 +- 7 files changed, 29 insertions(+), 43 deletions(-) diff --git a/tsc/cmd/tsc/sys.go b/tsc/cmd/tsc/sys.go index 1d7dab0cbd5ec..d73dfc393fff2 100644 --- a/tsc/cmd/tsc/sys.go +++ b/tsc/cmd/tsc/sys.go @@ -61,11 +61,7 @@ func (s *osSys) GetWidthOfTerminal() int { return width } -func (s *osSys) GetEnvironmentVariable(name string) string { - return os.Getenv(name) -} - -func (s *osSys) LookupEnvironmentVariable(name string) (string, bool) { +func (s *osSys) GetEnvironmentVariable(name string) (string, bool) { return os.LookupEnv(name) } diff --git a/tsc/internal/execute/build/orchestrator.go b/tsc/internal/execute/build/orchestrator.go index b896976819e08..06a69adb7b687 100644 --- a/tsc/internal/execute/build/orchestrator.go +++ b/tsc/internal/execute/build/orchestrator.go @@ -265,7 +265,7 @@ func (o *Orchestrator) Watch(ctx context.Context) { o.wm.Lock() if o.opts.Testing == nil { - if o.opts.Sys.GetEnvironmentVariable("TS_WATCH_DEBUG") != "" { + if value, _ := o.opts.Sys.GetEnvironmentVariable("TS_WATCH_DEBUG"); value != "" { o.wm.DebugLog = o.opts.Sys.Writer() } o.wm.EnsureDefaultBackend() diff --git a/tsc/internal/execute/tsc/compile.go b/tsc/internal/execute/tsc/compile.go index c61a6b938620b..a2f00d91f81f5 100644 --- a/tsc/internal/execute/tsc/compile.go +++ b/tsc/internal/execute/tsc/compile.go @@ -27,8 +27,7 @@ type System interface { GetCurrentDirectory() string WriteOutputIsTTY() bool GetWidthOfTerminal() int - GetEnvironmentVariable(name string) string - LookupEnvironmentVariable(name string) (string, bool) + GetEnvironmentVariable(name string) (string, bool) Spawn(command []string, dir string, stderr io.Writer) (io.ReadWriteCloser, error) Now() time.Time @@ -36,7 +35,7 @@ type System interface { } func newContentMapperLogger(sys System) contentmapper.Logger { - if sys.GetEnvironmentVariable("TS_CONTENT_MAPPER_DEBUG") == "" { + if value, _ := sys.GetEnvironmentVariable("TS_CONTENT_MAPPER_DEBUG"); value == "" { return nil } writer := sys.ErrorWriter() diff --git a/tsc/internal/execute/tsc/diagnostics.go b/tsc/internal/execute/tsc/diagnostics.go index 6d75e8d56c0b5..c4fa7b5ba6f41 100644 --- a/tsc/internal/execute/tsc/diagnostics.go +++ b/tsc/internal/execute/tsc/diagnostics.go @@ -44,7 +44,7 @@ func CreateDiagnosticReporter(sys System, w io.Writer, locale locale.Locale, opt } func defaultIsPretty(sys System) bool { - if forceColor, ok := sys.LookupEnvironmentVariable("FORCE_COLOR"); ok { + if forceColor, ok := sys.GetEnvironmentVariable("FORCE_COLOR"); ok { switch forceColor { case "", "1", "2", "3", "true": return true @@ -52,10 +52,10 @@ func defaultIsPretty(sys System) bool { return false } } - if sys.GetEnvironmentVariable("NO_COLOR") != "" { + if noColor, _ := sys.GetEnvironmentVariable("NO_COLOR"); noColor != "" { return false } - if sys.GetEnvironmentVariable("TERM") == "dumb" { + if term, _ := sys.GetEnvironmentVariable("TERM"); term == "dumb" { return false } return sys.WriteOutputIsTTY() @@ -82,18 +82,19 @@ func createColors(sys System) *colors { return &colors{showColors: false} } - os := sys.GetEnvironmentVariable("OS") + 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" + wtSession, _ := sys.GetEnvironmentVariable("WT_SESSION") + termProgram, _ := sys.GetEnvironmentVariable("TERM_PROGRAM") + colorTerm, _ := sys.GetEnvironmentVariable("COLORTERM") + term, _ := sys.GetEnvironmentVariable("TERM") return &colors{ showColors: true, isWindows: isWindows, - isWindowsTerminal: isWindowsTerminal, - isVSCode: isVSCode, - supportsRicherColors: supportsRicherColors, + isWindowsTerminal: wtSession != "", + isVSCode: termProgram == "vscode", + supportsRicherColors: colorTerm == "truecolor" || term == "xterm-256color", } } diff --git a/tsc/internal/execute/tsc/emit_test.go b/tsc/internal/execute/tsc/emit_test.go index d2af648784948..144876457e3e9 100644 --- a/tsc/internal/execute/tsc/emit_test.go +++ b/tsc/internal/execute/tsc/emit_test.go @@ -25,16 +25,11 @@ type contentMapperLoggingTestSystem struct { stderr bytes.Buffer } -func (s *contentMapperLoggingTestSystem) GetEnvironmentVariable(name string) string { +func (s *contentMapperLoggingTestSystem) GetEnvironmentVariable(name string) (string, bool) { if name == "TS_CONTENT_MAPPER_DEBUG" && s.enabled { - return "1" + return "1", true } - return "" -} - -func (s *contentMapperLoggingTestSystem) LookupEnvironmentVariable(name string) (string, bool) { - value := s.GetEnvironmentVariable(name) - return value, value != "" + return "", false } func (s *contentMapperLoggingTestSystem) ErrorWriter() io.Writer { @@ -111,15 +106,14 @@ type timingTestSystem struct { clock *controlledClock } -func (s *timingTestSystem) Writer() io.Writer { return io.Discard } -func (s *timingTestSystem) ErrorWriter() io.Writer { return io.Discard } -func (s *timingTestSystem) FS() vfs.FS { return s.fs } -func (s *timingTestSystem) DefaultLibraryPath() string { return "/lib" } -func (s *timingTestSystem) GetCurrentDirectory() string { return "/project" } -func (s *timingTestSystem) WriteOutputIsTTY() bool { return false } -func (s *timingTestSystem) GetWidthOfTerminal() int { return 0 } -func (s *timingTestSystem) GetEnvironmentVariable(name string) string { return "" } -func (s *timingTestSystem) LookupEnvironmentVariable(name string) (string, bool) { +func (s *timingTestSystem) Writer() io.Writer { return io.Discard } +func (s *timingTestSystem) ErrorWriter() io.Writer { return io.Discard } +func (s *timingTestSystem) FS() vfs.FS { return s.fs } +func (s *timingTestSystem) DefaultLibraryPath() string { return "/lib" } +func (s *timingTestSystem) GetCurrentDirectory() string { return "/project" } +func (s *timingTestSystem) WriteOutputIsTTY() bool { return false } +func (s *timingTestSystem) GetWidthOfTerminal() int { return 0 } +func (s *timingTestSystem) GetEnvironmentVariable(name string) (string, bool) { return "", false } func (s *timingTestSystem) Now() time.Time { return s.clock.Now() } diff --git a/tsc/internal/execute/tsctests/sys.go b/tsc/internal/execute/tsctests/sys.go index 03fca0eaaedb1..98d528c291f40 100644 --- a/tsc/internal/execute/tsctests/sys.go +++ b/tsc/internal/execute/tsctests/sys.go @@ -235,17 +235,13 @@ func (s *TestSys) WriteOutputIsTTY() bool { } func (s *TestSys) GetWidthOfTerminal() int { - if widthStr := s.GetEnvironmentVariable("TS_TEST_TERMINAL_WIDTH"); widthStr != "" { + if widthStr, _ := s.GetEnvironmentVariable("TS_TEST_TERMINAL_WIDTH"); widthStr != "" { return core.Must(strconv.Atoi(widthStr)) } return 0 } -func (s *TestSys) GetEnvironmentVariable(name string) string { - return s.env[name] -} - -func (s *TestSys) LookupEnvironmentVariable(name string) (string, bool) { +func (s *TestSys) GetEnvironmentVariable(name string) (string, bool) { value, ok := s.env[name] return value, ok } diff --git a/tsc/internal/execute/watcher.go b/tsc/internal/execute/watcher.go index 0679fc0eea59e..e08d214450c0d 100644 --- a/tsc/internal/execute/watcher.go +++ b/tsc/internal/execute/watcher.go @@ -148,7 +148,7 @@ func (w *Watcher) start(ctx context.Context) { w.configFilePaths = append([]string{w.configFileName}, w.config.ExtendedSourceFiles()...) } - if w.sys.GetEnvironmentVariable("TS_WATCH_DEBUG") != "" { + if value, _ := w.sys.GetEnvironmentVariable("TS_WATCH_DEBUG"); value != "" { w.wm.DebugLog = w.sys.Writer() }