From 9013718a4621dbc7ec219774c6abb473dafe28cc Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 23 Jun 2026 07:03:38 +0000
Subject: [PATCH 1/4] Initial plan
From 256b95984177ed7262f9798fae73d8e2415e6b9a Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 23 Jun 2026 07:18:49 +0000
Subject: [PATCH 2/4] Fix diagnostic CLI exit code
Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com>
---
cmd/tsgo/main.go | 10 +++++++++-
cmd/tsgo/main_test.go | 19 +++++++++++++++++++
2 files changed, 28 insertions(+), 1 deletion(-)
create mode 100644 cmd/tsgo/main_test.go
diff --git a/cmd/tsgo/main.go b/cmd/tsgo/main.go
index 8d6816fa3d4..d3c044a4457 100644
--- a/cmd/tsgo/main.go
+++ b/cmd/tsgo/main.go
@@ -8,6 +8,7 @@ import (
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/execute"
+ "github.com/microsoft/typescript-go/internal/execute/tsc"
)
func main() {
@@ -28,5 +29,12 @@ func runMain() int {
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
result := execute.CommandLine(ctx, newSystem(), args, nil)
- return int(result.Status)
+ return exitCode(result.Status)
+}
+
+func exitCode(status tsc.ExitStatus) int {
+ if status == tsc.ExitStatusDiagnosticsPresent_OutputsSkipped {
+ return 2
+ }
+ return int(status)
}
diff --git a/cmd/tsgo/main_test.go b/cmd/tsgo/main_test.go
new file mode 100644
index 00000000000..5e7b972b3ed
--- /dev/null
+++ b/cmd/tsgo/main_test.go
@@ -0,0 +1,19 @@
+package main
+
+import (
+ "testing"
+
+ "github.com/microsoft/typescript-go/internal/execute/tsc"
+ "gotest.tools/v3/assert"
+)
+
+func TestExitCode(t *testing.T) {
+ t.Parallel()
+
+ assert.Equal(t, exitCode(tsc.ExitStatusSuccess), 0)
+ assert.Equal(t, exitCode(tsc.ExitStatusDiagnosticsPresent_OutputsSkipped), 2)
+ assert.Equal(t, exitCode(tsc.ExitStatusDiagnosticsPresent_OutputsGenerated), 2)
+ assert.Equal(t, exitCode(tsc.ExitStatusInvalidProject_OutputsSkipped), 3)
+ assert.Equal(t, exitCode(tsc.ExitStatusProjectReferenceCycle_OutputsSkipped), 4)
+ assert.Equal(t, exitCode(tsc.ExitStatusNotImplemented), 5)
+}
From c156501c5cfc91fbf1856915ae9220581599d5eb Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 23 Jun 2026 07:21:24 +0000
Subject: [PATCH 3/4] Make diagnostic exit mapping explicit
Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com>
---
cmd/tsgo/main.go | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/cmd/tsgo/main.go b/cmd/tsgo/main.go
index d3c044a4457..43d8a78074c 100644
--- a/cmd/tsgo/main.go
+++ b/cmd/tsgo/main.go
@@ -33,7 +33,8 @@ func runMain() int {
}
func exitCode(status tsc.ExitStatus) int {
- if status == tsc.ExitStatusDiagnosticsPresent_OutputsSkipped {
+ switch status {
+ case tsc.ExitStatusDiagnosticsPresent_OutputsSkipped, tsc.ExitStatusDiagnosticsPresent_OutputsGenerated:
return 2
}
return int(status)
From cc8ea6993713f4b7d90e4d327ac9e486421d480f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 23 Jun 2026 17:55:12 +0000
Subject: [PATCH 4/4] Restore noEmit exit code via handleNoEmitOptions port
Port tsc's handleNoEmitOptions in Program.Emit: a whole-program noEmit is
not skipped (only a single-file emit is), so a program with diagnostics
reports DiagnosticsPresent_OutputsGenerated, matching tsc and the process
exit code 2 that older tsgo releases produced.
Revert the earlier cmd/tsgo exit-code mapping hack and its test; add a tsc
command-line regression test instead.
Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com>
---
cmd/tsgo/main.go | 11 +----
cmd/tsgo/main_test.go | 19 ---------
internal/compiler/program.go | 5 +++
internal/execute/tsctests/tsc_test.go | 5 +++
.../tsc/commandLine/noEmit-with-type-error.js | 41 +++++++++++++++++++
.../generateTrace-generates-types-file.js | 4 +-
...e-with-multiple-files-and-complex-types.js | 8 +---
.../reference/tsc/noEmit/dts-errors.js | 8 ++--
.../reference/tsc/noEmit/semantic-errors.js | 8 ++--
.../reference/tsc/noEmit/syntax-errors.js | 8 ++--
10 files changed, 67 insertions(+), 50 deletions(-)
delete mode 100644 cmd/tsgo/main_test.go
create mode 100644 testdata/baselines/reference/tsc/commandLine/noEmit-with-type-error.js
diff --git a/cmd/tsgo/main.go b/cmd/tsgo/main.go
index 43d8a78074c..8d6816fa3d4 100644
--- a/cmd/tsgo/main.go
+++ b/cmd/tsgo/main.go
@@ -8,7 +8,6 @@ import (
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/execute"
- "github.com/microsoft/typescript-go/internal/execute/tsc"
)
func main() {
@@ -29,13 +28,5 @@ func runMain() int {
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
result := execute.CommandLine(ctx, newSystem(), args, nil)
- return exitCode(result.Status)
-}
-
-func exitCode(status tsc.ExitStatus) int {
- switch status {
- case tsc.ExitStatusDiagnosticsPresent_OutputsSkipped, tsc.ExitStatusDiagnosticsPresent_OutputsGenerated:
- return 2
- }
- return int(status)
+ return int(result.Status)
}
diff --git a/cmd/tsgo/main_test.go b/cmd/tsgo/main_test.go
deleted file mode 100644
index 5e7b972b3ed..00000000000
--- a/cmd/tsgo/main_test.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package main
-
-import (
- "testing"
-
- "github.com/microsoft/typescript-go/internal/execute/tsc"
- "gotest.tools/v3/assert"
-)
-
-func TestExitCode(t *testing.T) {
- t.Parallel()
-
- assert.Equal(t, exitCode(tsc.ExitStatusSuccess), 0)
- assert.Equal(t, exitCode(tsc.ExitStatusDiagnosticsPresent_OutputsSkipped), 2)
- assert.Equal(t, exitCode(tsc.ExitStatusDiagnosticsPresent_OutputsGenerated), 2)
- assert.Equal(t, exitCode(tsc.ExitStatusInvalidProject_OutputsSkipped), 3)
- assert.Equal(t, exitCode(tsc.ExitStatusProjectReferenceCycle_OutputsSkipped), 4)
- assert.Equal(t, exitCode(tsc.ExitStatusNotImplemented), 5)
-}
diff --git a/internal/compiler/program.go b/internal/compiler/program.go
index 35093fab681..a4bc6fca222 100644
--- a/internal/compiler/program.go
+++ b/internal/compiler/program.go
@@ -1631,6 +1631,11 @@ func (p *Program) Emit(ctx context.Context, options EmitOptions) *EmitResult {
}
if options.EmitOnly != EmitOnlyForcedDts {
+ if p.Options().NoEmit.IsTrue() {
+ // Mirror handleNoEmitOptions: a single-file noEmit is skipped, but a whole-program
+ // noEmit is not, so a program with diagnostics reports DiagnosticsPresent_OutputsGenerated.
+ return &EmitResult{EmitSkipped: options.TargetSourceFile != nil}
+ }
result := HandleNoEmitOnError(
ctx,
p,
diff --git a/internal/execute/tsctests/tsc_test.go b/internal/execute/tsctests/tsc_test.go
index 46128bbb759..693184fcce0 100644
--- a/internal/execute/tsctests/tsc_test.go
+++ b/internal/execute/tsctests/tsc_test.go
@@ -118,6 +118,11 @@ func TestTscCommandline(t *testing.T) {
files: FileMap{"/home/src/workspaces/project/first.ts": `export const Key = Symbol()`},
commandLineArgs: []string{"--lib", "es6 ", "first.ts"},
},
+ {
+ subScenario: "noEmit with type error",
+ files: FileMap{"/home/src/workspaces/project/index.ts": `x = 5;`},
+ commandLineArgs: []string{"--noEmit", "index.ts"},
+ },
{
subScenario: "option diagnostics are suppressed when there are syntactic errors",
files: FileMap{"/home/src/workspaces/project/a.ts": `const x: = 1;`},
diff --git a/testdata/baselines/reference/tsc/commandLine/noEmit-with-type-error.js b/testdata/baselines/reference/tsc/commandLine/noEmit-with-type-error.js
new file mode 100644
index 00000000000..fbe6848a262
--- /dev/null
+++ b/testdata/baselines/reference/tsc/commandLine/noEmit-with-type-error.js
@@ -0,0 +1,41 @@
+currentDirectory::/home/src/workspaces/project
+useCaseSensitiveFileNames::true
+Input::
+//// [/home/src/workspaces/project/index.ts] *new*
+x = 5;
+
+tsgo --noEmit index.ts
+ExitStatus:: DiagnosticsPresent_OutputsGenerated
+Output::
+[96mindex.ts[0m:[93m1[0m:[93m1[0m - [91merror[0m[90m TS2304: [0mCannot find name 'x'.
+
+[7m1[0m x = 5;
+[7m [0m [91m~[0m
+
+
+Found 1 error in index.ts[90m:1[0m
+
+//// [/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/testdata/baselines/reference/tsc/generateTrace/generateTrace-generates-types-file.js b/testdata/baselines/reference/tsc/generateTrace/generateTrace-generates-types-file.js
index 7905bb21b6b..ac1c0aa7a47 100644
--- a/testdata/baselines/reference/tsc/generateTrace/generateTrace-generates-types-file.js
+++ b/testdata/baselines/reference/tsc/generateTrace/generateTrace-generates-types-file.js
@@ -77,9 +77,7 @@ declare const console: { log(msg: any): void; };
{"pid":1,"tid":2,"ph":"E","cat":"check","ts":18,"name":"checkSourceFile","args":{"checkerId":0,"path":"/home/src/workspaces/project/a.ts"}},
{"pid":1,"tid":1,"ph":"E","cat":"check","ts":19,"name":"checkSourceFiles"},
{"pid":1,"tid":1,"ph":"B","cat":"emit","ts":20,"name":"emit"},
-{"pid":1,"tid":354130385,"ph":"B","cat":"emit","ts":21,"name":"emit","args":{"path":"/home/src/workspaces/project/a.ts"}},
-{"pid":1,"tid":354130385,"ph":"E","cat":"emit","ts":22,"name":"emit","args":{"path":"/home/src/workspaces/project/a.ts"}},
-{"pid":1,"tid":1,"ph":"E","cat":"emit","ts":23,"name":"emit"}
+{"pid":1,"tid":1,"ph":"E","cat":"emit","ts":21,"name":"emit"}
]
//// [/home/src/workspaces/project/trace/types_0.json] *new*
diff --git a/testdata/baselines/reference/tsc/generateTrace/generateTrace-with-multiple-files-and-complex-types.js b/testdata/baselines/reference/tsc/generateTrace/generateTrace-with-multiple-files-and-complex-types.js
index d981e92cb2f..befa8be5368 100644
--- a/testdata/baselines/reference/tsc/generateTrace/generateTrace-with-multiple-files-and-complex-types.js
+++ b/testdata/baselines/reference/tsc/generateTrace/generateTrace-with-multiple-files-and-complex-types.js
@@ -20,7 +20,7 @@ export interface Container {
export type Nullable = T | null | undefined;
tsgo --generateTrace /home/src/workspaces/project/trace --singleThreaded
-ExitStatus:: DiagnosticsPresent_OutputsSkipped
+ExitStatus:: DiagnosticsPresent_OutputsGenerated
Output::
[96mmain.ts[0m:[93m2[0m:[93m74[0m - [91merror[0m[90m TS2322: [0mType '(fn: (x: number) => U) => Container' is not assignable to type '(fn: (x: U) => U) => Container'.
Types of parameters 'fn' and 'fn' are incompatible.
@@ -104,11 +104,7 @@ declare const console: { log(msg: any): void; };
{"pid":1,"tid":2,"ph":"E","cat":"check","ts":24,"name":"checkSourceFile","args":{"checkerId":0,"path":"/home/src/workspaces/project/main.ts"}},
{"pid":1,"tid":1,"ph":"E","cat":"check","ts":25,"name":"checkSourceFiles"},
{"pid":1,"tid":1,"ph":"B","cat":"emit","ts":26,"name":"emit"},
-{"pid":1,"tid":100209852,"ph":"B","cat":"emit","ts":27,"name":"emit","args":{"path":"/home/src/workspaces/project/main.ts"}},
-{"pid":1,"tid":100209852,"ph":"E","cat":"emit","ts":28,"name":"emit","args":{"path":"/home/src/workspaces/project/main.ts"}},
-{"pid":1,"tid":553096334,"ph":"B","cat":"emit","ts":29,"name":"emit","args":{"path":"/home/src/workspaces/project/types.ts"}},
-{"pid":1,"tid":553096334,"ph":"E","cat":"emit","ts":30,"name":"emit","args":{"path":"/home/src/workspaces/project/types.ts"}},
-{"pid":1,"tid":1,"ph":"E","cat":"emit","ts":31,"name":"emit"}
+{"pid":1,"tid":1,"ph":"E","cat":"emit","ts":27,"name":"emit"}
]
//// [/home/src/workspaces/project/trace/types_0.json] *new*
diff --git a/testdata/baselines/reference/tsc/noEmit/dts-errors.js b/testdata/baselines/reference/tsc/noEmit/dts-errors.js
index 67254f6a2b2..299fdcb8dac 100644
--- a/testdata/baselines/reference/tsc/noEmit/dts-errors.js
+++ b/testdata/baselines/reference/tsc/noEmit/dts-errors.js
@@ -12,7 +12,7 @@ const a = class { private p = 10; };
}
tsgo --noEmit
-ExitStatus:: DiagnosticsPresent_OutputsSkipped
+ExitStatus:: DiagnosticsPresent_OutputsGenerated
Output::
[96ma.ts[0m:[93m1[0m:[93m7[0m - [91merror[0m[90m TS4094: [0mProperty 'p' of exported anonymous class type may not be private or protected.
@@ -55,7 +55,7 @@ declare const console: { log(msg: any): void; };
Edit [0]:: no change
tsgo --noEmit
-ExitStatus:: DiagnosticsPresent_OutputsSkipped
+ExitStatus:: DiagnosticsPresent_OutputsGenerated
Output::
[96ma.ts[0m:[93m1[0m:[93m7[0m - [91merror[0m[90m TS4094: [0mProperty 'p' of exported anonymous class type may not be private or protected.
@@ -118,7 +118,7 @@ Edit [5]:: Introduce error
const a = class { private p = 10; };
tsgo --noEmit
-ExitStatus:: DiagnosticsPresent_OutputsSkipped
+ExitStatus:: DiagnosticsPresent_OutputsGenerated
Output::
[96ma.ts[0m:[93m1[0m:[93m7[0m - [91merror[0m[90m TS4094: [0mProperty 'p' of exported anonymous class type may not be private or protected.
@@ -164,7 +164,7 @@ const a = class {
Edit [7]:: no change
tsgo --noEmit
-ExitStatus:: DiagnosticsPresent_OutputsSkipped
+ExitStatus:: DiagnosticsPresent_OutputsGenerated
Output::
[96ma.ts[0m:[93m1[0m:[93m7[0m - [91merror[0m[90m TS4094: [0mProperty 'p' of exported anonymous class type may not be private or protected.
diff --git a/testdata/baselines/reference/tsc/noEmit/semantic-errors.js b/testdata/baselines/reference/tsc/noEmit/semantic-errors.js
index 96ff47848c8..78f6e09630f 100644
--- a/testdata/baselines/reference/tsc/noEmit/semantic-errors.js
+++ b/testdata/baselines/reference/tsc/noEmit/semantic-errors.js
@@ -12,7 +12,7 @@ const a: number = "hello"
}
tsgo --noEmit
-ExitStatus:: DiagnosticsPresent_OutputsSkipped
+ExitStatus:: DiagnosticsPresent_OutputsGenerated
Output::
[96ma.ts[0m:[93m1[0m:[93m7[0m - [91merror[0m[90m TS2322: [0mType 'string' is not assignable to type 'number'.
@@ -51,7 +51,7 @@ declare const console: { log(msg: any): void; };
Edit [0]:: no change
tsgo --noEmit
-ExitStatus:: DiagnosticsPresent_OutputsSkipped
+ExitStatus:: DiagnosticsPresent_OutputsGenerated
Output::
[96ma.ts[0m:[93m1[0m:[93m7[0m - [91merror[0m[90m TS2322: [0mType 'string' is not assignable to type 'number'.
@@ -107,7 +107,7 @@ Edit [5]:: Introduce error
const a: number = "hello"
tsgo --noEmit
-ExitStatus:: DiagnosticsPresent_OutputsSkipped
+ExitStatus:: DiagnosticsPresent_OutputsGenerated
Output::
[96ma.ts[0m:[93m1[0m:[93m7[0m - [91merror[0m[90m TS2322: [0mType 'string' is not assignable to type 'number'.
@@ -140,7 +140,7 @@ Found 1 error in a.ts[90m:1[0m
Edit [7]:: no change
tsgo --noEmit
-ExitStatus:: DiagnosticsPresent_OutputsSkipped
+ExitStatus:: DiagnosticsPresent_OutputsGenerated
Output::
[96ma.ts[0m:[93m1[0m:[93m7[0m - [91merror[0m[90m TS2322: [0mType 'string' is not assignable to type 'number'.
diff --git a/testdata/baselines/reference/tsc/noEmit/syntax-errors.js b/testdata/baselines/reference/tsc/noEmit/syntax-errors.js
index 5f1f5d6e7c4..8e28f48d7b3 100644
--- a/testdata/baselines/reference/tsc/noEmit/syntax-errors.js
+++ b/testdata/baselines/reference/tsc/noEmit/syntax-errors.js
@@ -12,7 +12,7 @@ const a = "hello
}
tsgo --noEmit
-ExitStatus:: DiagnosticsPresent_OutputsSkipped
+ExitStatus:: DiagnosticsPresent_OutputsGenerated
Output::
[96ma.ts[0m:[93m1[0m:[93m17[0m - [91merror[0m[90m TS1002: [0mUnterminated string literal.
@@ -51,7 +51,7 @@ declare const console: { log(msg: any): void; };
Edit [0]:: no change
tsgo --noEmit
-ExitStatus:: DiagnosticsPresent_OutputsSkipped
+ExitStatus:: DiagnosticsPresent_OutputsGenerated
Output::
[96ma.ts[0m:[93m1[0m:[93m17[0m - [91merror[0m[90m TS1002: [0mUnterminated string literal.
@@ -107,7 +107,7 @@ Edit [5]:: Introduce error
const a = "hello
tsgo --noEmit
-ExitStatus:: DiagnosticsPresent_OutputsSkipped
+ExitStatus:: DiagnosticsPresent_OutputsGenerated
Output::
[96ma.ts[0m:[93m1[0m:[93m17[0m - [91merror[0m[90m TS1002: [0mUnterminated string literal.
@@ -143,7 +143,7 @@ const a = "hello;
Edit [7]:: no change
tsgo --noEmit
-ExitStatus:: DiagnosticsPresent_OutputsSkipped
+ExitStatus:: DiagnosticsPresent_OutputsGenerated
Output::
[96ma.ts[0m:[93m1[0m:[93m17[0m - [91merror[0m[90m TS1002: [0mUnterminated string literal.