From 3816924e4c69fb5ede0b73be0ddb9ce8174f686f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 May 2026 22:57:53 +0000 Subject: [PATCH 1/4] Initial plan From 474d98dda3ce1d89b942b272d50934e091fb4d05 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 May 2026 23:21:06 +0000 Subject: [PATCH 2/4] Fix crash in checkExternalEmitHelpers when import helpers specifier is nil Handle nil return from GetImportHelpersImportSpecifier in resolveHelpersModule by falling back to the source file node. This prevents a nil pointer dereference in resolveExternalModule when the synthetic tslib import specifier wasn't created during file loading (e.g., in language service scenarios). Fixes #1358 Co-authored-by: DanielRosenwasser <972891+DanielRosenwasser@users.noreply.github.com> --- internal/checker/checker.go | 3 ++ .../checkExternalEmitHelpersCrash_test.go | 48 +++++++++++++++++++ .../compiler/checkExternalEmitHelpersCrash.js | 28 +++++++++++ .../compiler/checkExternalEmitHelpersCrash.ts | 19 ++++++++ 4 files changed, 98 insertions(+) create mode 100644 internal/fourslash/tests/checkExternalEmitHelpersCrash_test.go create mode 100644 testdata/baselines/reference/compiler/checkExternalEmitHelpersCrash.js create mode 100644 testdata/tests/cases/compiler/checkExternalEmitHelpersCrash.ts diff --git a/internal/checker/checker.go b/internal/checker/checker.go index 020fe611931..29aa4976ebd 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -28305,6 +28305,9 @@ func (c *Checker) resolveHelpersModule(file *ast.SourceFile, errorNode *ast.Node links := c.sourceFileLinks.Get(file) if links.externalHelpersModule == nil { location := c.program.GetImportHelpersImportSpecifier(file.Path()) + if location == nil { + location = file.AsNode() + } helpersModule := c.resolveExternalModule(location, externalHelpersModuleNameText, diagnostics.This_syntax_requires_an_imported_helper_but_module_0_cannot_be_found, errorNode, false /*isForAugmentation*/) if helpersModule == nil { helpersModule = c.unknownSymbol diff --git a/internal/fourslash/tests/checkExternalEmitHelpersCrash_test.go b/internal/fourslash/tests/checkExternalEmitHelpersCrash_test.go new file mode 100644 index 00000000000..d408e5abdc1 --- /dev/null +++ b/internal/fourslash/tests/checkExternalEmitHelpersCrash_test.go @@ -0,0 +1,48 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestCheckExternalEmitHelpersCrash(t *testing.T) { + t.Parallel() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = ` +// @Filename: /tsconfig.json +{ + "compilerOptions": { + "target": "es2015", + "module": "commonjs", + "importHelpers": true + } +} + +// @Filename: /node_modules/tslib/package.json +{ "name": "tslib", "main": "tslib.js", "typings": "tslib.d.ts" } + +// @Filename: /node_modules/tslib/tslib.d.ts +export declare function __awaiter(thisArg: any, _arguments: any, P: Function, generator: Function): any; +export declare function __decorate(decorators: Function[], target: any, key?: string | symbol, desc?: any): any; +export declare function __esDecorate(ctor: any, descriptorIn: any, decorators: any, contextIn: any, initializers: any, extraInitializers: any): void; +export declare function __runInitializers(thisArg: any, initializers: any, value?: any): any; +export declare function __setFunctionName(f: any, name: any, prefix?: string): any; + +// @Filename: /main.ts +export async function doStuff() { + return 1; +} + +function decorator(target: any, context: any) {} + +export +@decorator +class /*1*/MyClass { +} +` + f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) + defer done() + f.VerifyQuickInfoAt(t, "1", "class MyClass", "") +} diff --git a/testdata/baselines/reference/compiler/checkExternalEmitHelpersCrash.js b/testdata/baselines/reference/compiler/checkExternalEmitHelpersCrash.js new file mode 100644 index 00000000000..5380c73d06b --- /dev/null +++ b/testdata/baselines/reference/compiler/checkExternalEmitHelpersCrash.js @@ -0,0 +1,28 @@ +//// [tests/cases/compiler/checkExternalEmitHelpersCrash.ts] //// + +//// [package.json] +{ + "name": "tslib", + "main": "tslib.js", + "typings": "tslib.d.ts" +} + +//// [tslib.d.ts] +export declare function __awaiter(thisArg: any, _arguments: any, P: Function, generator: Function): any; + +//// [main.ts] +export async function doStuff() { + return 1; +} + + +//// [main.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.doStuff = doStuff; +const tslib_1 = require("tslib"); +function doStuff() { + return tslib_1.__awaiter(this, void 0, void 0, function* () { + return 1; + }); +} diff --git a/testdata/tests/cases/compiler/checkExternalEmitHelpersCrash.ts b/testdata/tests/cases/compiler/checkExternalEmitHelpersCrash.ts new file mode 100644 index 00000000000..0c3ea0106a8 --- /dev/null +++ b/testdata/tests/cases/compiler/checkExternalEmitHelpersCrash.ts @@ -0,0 +1,19 @@ +// @noTypesAndSymbols: true +// @target: es2015 +// @module: commonjs +// @importHelpers: true + +// @filename: /node_modules/tslib/package.json +{ + "name": "tslib", + "main": "tslib.js", + "typings": "tslib.d.ts" +} + +// @filename: /node_modules/tslib/tslib.d.ts +export declare function __awaiter(thisArg: any, _arguments: any, P: Function, generator: Function): any; + +// @filename: /main.ts +export async function doStuff() { + return 1; +} From 849a14fb3430a503e2c71441f6687dda716b81db Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 May 2026 23:21:54 +0000 Subject: [PATCH 3/4] Enhance fourslash test to verify both async and decorator code paths Co-authored-by: DanielRosenwasser <972891+DanielRosenwasser@users.noreply.github.com> --- .../fourslash/tests/checkExternalEmitHelpersCrash_test.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/internal/fourslash/tests/checkExternalEmitHelpersCrash_test.go b/internal/fourslash/tests/checkExternalEmitHelpersCrash_test.go index d408e5abdc1..4c5e59dee0d 100644 --- a/internal/fourslash/tests/checkExternalEmitHelpersCrash_test.go +++ b/internal/fourslash/tests/checkExternalEmitHelpersCrash_test.go @@ -31,7 +31,7 @@ export declare function __runInitializers(thisArg: any, initializers: any, value export declare function __setFunctionName(f: any, name: any, prefix?: string): any; // @Filename: /main.ts -export async function doStuff() { +export async function /*1*/doStuff() { return 1; } @@ -39,10 +39,11 @@ function decorator(target: any, context: any) {} export @decorator -class /*1*/MyClass { +class /*2*/MyClass { } ` f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) defer done() - f.VerifyQuickInfoAt(t, "1", "class MyClass", "") + f.VerifyQuickInfoAt(t, "1", "function doStuff(): Promise", "") + f.VerifyQuickInfoAt(t, "2", "class MyClass", "") } From 11b59a8d5532b3d4ad770fa018ccfe023f9d6dbc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 18 May 2026 17:47:09 +0000 Subject: [PATCH 4/4] Remove tests that don't reproduce the panic The fourslash and compiler tests were not triggering the nil dereference. The specifier is always created for files that pass IsEffectiveExternalModule under normal program construction, so the crash likely requires a specific LS scenario (e.g., project references with differing importHelpers settings) that is difficult to reproduce in unit tests. The defensive nil check in resolveHelpersModule is retained. Co-authored-by: DanielRosenwasser <972891+DanielRosenwasser@users.noreply.github.com> --- .../checkExternalEmitHelpersCrash_test.go | 49 ------------------- .../compiler/checkExternalEmitHelpersCrash.js | 28 ----------- .../compiler/checkExternalEmitHelpersCrash.ts | 19 ------- 3 files changed, 96 deletions(-) delete mode 100644 internal/fourslash/tests/checkExternalEmitHelpersCrash_test.go delete mode 100644 testdata/baselines/reference/compiler/checkExternalEmitHelpersCrash.js delete mode 100644 testdata/tests/cases/compiler/checkExternalEmitHelpersCrash.ts diff --git a/internal/fourslash/tests/checkExternalEmitHelpersCrash_test.go b/internal/fourslash/tests/checkExternalEmitHelpersCrash_test.go deleted file mode 100644 index 4c5e59dee0d..00000000000 --- a/internal/fourslash/tests/checkExternalEmitHelpersCrash_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package fourslash_test - -import ( - "testing" - - "github.com/microsoft/typescript-go/internal/fourslash" - "github.com/microsoft/typescript-go/internal/testutil" -) - -func TestCheckExternalEmitHelpersCrash(t *testing.T) { - t.Parallel() - defer testutil.RecoverAndFail(t, "Panic on fourslash test") - const content = ` -// @Filename: /tsconfig.json -{ - "compilerOptions": { - "target": "es2015", - "module": "commonjs", - "importHelpers": true - } -} - -// @Filename: /node_modules/tslib/package.json -{ "name": "tslib", "main": "tslib.js", "typings": "tslib.d.ts" } - -// @Filename: /node_modules/tslib/tslib.d.ts -export declare function __awaiter(thisArg: any, _arguments: any, P: Function, generator: Function): any; -export declare function __decorate(decorators: Function[], target: any, key?: string | symbol, desc?: any): any; -export declare function __esDecorate(ctor: any, descriptorIn: any, decorators: any, contextIn: any, initializers: any, extraInitializers: any): void; -export declare function __runInitializers(thisArg: any, initializers: any, value?: any): any; -export declare function __setFunctionName(f: any, name: any, prefix?: string): any; - -// @Filename: /main.ts -export async function /*1*/doStuff() { - return 1; -} - -function decorator(target: any, context: any) {} - -export -@decorator -class /*2*/MyClass { -} -` - f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) - defer done() - f.VerifyQuickInfoAt(t, "1", "function doStuff(): Promise", "") - f.VerifyQuickInfoAt(t, "2", "class MyClass", "") -} diff --git a/testdata/baselines/reference/compiler/checkExternalEmitHelpersCrash.js b/testdata/baselines/reference/compiler/checkExternalEmitHelpersCrash.js deleted file mode 100644 index 5380c73d06b..00000000000 --- a/testdata/baselines/reference/compiler/checkExternalEmitHelpersCrash.js +++ /dev/null @@ -1,28 +0,0 @@ -//// [tests/cases/compiler/checkExternalEmitHelpersCrash.ts] //// - -//// [package.json] -{ - "name": "tslib", - "main": "tslib.js", - "typings": "tslib.d.ts" -} - -//// [tslib.d.ts] -export declare function __awaiter(thisArg: any, _arguments: any, P: Function, generator: Function): any; - -//// [main.ts] -export async function doStuff() { - return 1; -} - - -//// [main.js] -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.doStuff = doStuff; -const tslib_1 = require("tslib"); -function doStuff() { - return tslib_1.__awaiter(this, void 0, void 0, function* () { - return 1; - }); -} diff --git a/testdata/tests/cases/compiler/checkExternalEmitHelpersCrash.ts b/testdata/tests/cases/compiler/checkExternalEmitHelpersCrash.ts deleted file mode 100644 index 0c3ea0106a8..00000000000 --- a/testdata/tests/cases/compiler/checkExternalEmitHelpersCrash.ts +++ /dev/null @@ -1,19 +0,0 @@ -// @noTypesAndSymbols: true -// @target: es2015 -// @module: commonjs -// @importHelpers: true - -// @filename: /node_modules/tslib/package.json -{ - "name": "tslib", - "main": "tslib.js", - "typings": "tslib.d.ts" -} - -// @filename: /node_modules/tslib/tslib.d.ts -export declare function __awaiter(thisArg: any, _arguments: any, P: Function, generator: Function): any; - -// @filename: /main.ts -export async function doStuff() { - return 1; -}