From 2a5f01aacbd3c332abf23fc67596a55fdf39f986 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 19 May 2026 20:52:09 +0000 Subject: [PATCH 1/7] Initial plan From 083640cfbb96f521fba7fd7c0e353cf49d235b8c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 19 May 2026 21:11:10 +0000 Subject: [PATCH 2/7] Fix helper checks after module update Co-authored-by: DanielRosenwasser <972891+DanielRosenwasser@users.noreply.github.com> --- internal/checker/checker_test.go | 52 ++++++++++++++++++++++++++++++++ internal/compiler/program.go | 2 ++ 2 files changed, 54 insertions(+) diff --git a/internal/checker/checker_test.go b/internal/checker/checker_test.go index 00d93a65f2c..74c44cdca81 100644 --- a/internal/checker/checker_test.go +++ b/internal/checker/checker_test.go @@ -9,6 +9,7 @@ import ( "github.com/microsoft/typescript-go/internal/compiler" "github.com/microsoft/typescript-go/internal/core" "github.com/microsoft/typescript-go/internal/repo" + "github.com/microsoft/typescript-go/internal/testutil" "github.com/microsoft/typescript-go/internal/tsoptions" "github.com/microsoft/typescript-go/internal/tspath" "github.com/microsoft/typescript-go/internal/vfs/osvfs" @@ -61,6 +62,57 @@ foo.bar;` } } +func TestImportHelpersAfterProgramUpdateWithoutSyntheticImportSpecifier(t *testing.T) { + t.Parallel() + defer testutil.RecoverAndFail(t, "Panic on import helpers after program update") + + const config = `{ + "compilerOptions": { + "target": "es2015", + "module": "commonjs", + "experimentalDecorators": true, + "importHelpers": true + }, + "files": ["foo.ts"] + }` + const original = `declare function dec(value: Function): void; +class C {}` + const updated = `declare function dec(value: Function): void; +@dec +export class C {}` + const tslib = `export declare function __decorate(...args: any[]): any;` + + fs := bundled.WrapFS(vfstest.FromMap(map[string]string{ + "/foo.ts": original, + "/node_modules/tslib/package.json": `{"name":"tslib","typings":"tslib.d.ts"}`, + "/node_modules/tslib/tslib.d.ts": tslib, + "/node_modules/tslib/tslib.js": "", + "/tsconfig.json": config, + }, false /*useCaseSensitiveFileNames*/)) + host := compiler.NewCompilerHost("/", fs, bundled.LibPath(), nil, nil) + parsed, errors := tsoptions.GetParsedCommandLineOfConfigFile("/tsconfig.json", &core.CompilerOptions{}, nil, host, nil) + assert.Equal(t, len(errors), 0, "Expected no errors in parsed command line") + + p := compiler.NewProgram(compiler.ProgramOptions{ + Config: parsed, + Host: host, + }) + path := p.GetSourceFile("/foo.ts").Path() + + updatedFS := bundled.WrapFS(vfstest.FromMap(map[string]string{ + "/foo.ts": updated, + "/node_modules/tslib/package.json": `{"name":"tslib","typings":"tslib.d.ts"}`, + "/node_modules/tslib/tslib.d.ts": tslib, + "/node_modules/tslib/tslib.js": "", + "/tsconfig.json": config, + }, false /*useCaseSensitiveFileNames*/)) + updatedHost := compiler.NewCompilerHost("/", updatedFS, bundled.LibPath(), nil, nil) + updatedProgram, _ := p.UpdateProgram(path, updatedHost, nil) + updatedFile := updatedProgram.GetSourceFile("/foo.ts") + diagnostics := updatedProgram.GetSemanticDiagnostics(t.Context(), updatedFile) + assert.Equal(t, len(diagnostics), 0, "Expected no semantic diagnostics") +} + func BenchmarkNewChecker(b *testing.B) { repo.SkipIfNoTypeScriptSubmodule(b) fs := osvfs.FS() diff --git a/internal/compiler/program.go b/internal/compiler/program.go index 88a43728268..ddf3023f147 100644 --- a/internal/compiler/program.go +++ b/internal/compiler/program.go @@ -343,6 +343,8 @@ func canReplaceFileInProgram(file1 *ast.SourceFile, file2 *ast.SourceFile) bool return file2 != nil && file1.ParseOptions() == file2.ParseOptions() && file1.UsesUriStyleNodeCoreModules == file2.UsesUriStyleNodeCoreModules && + (file1.ExternalModuleIndicator != nil) == (file2.ExternalModuleIndicator != nil) && + (file1.CommonJSModuleIndicator != nil) == (file2.CommonJSModuleIndicator != nil) && slices.EqualFunc(file1.Imports(), file2.Imports(), equalModuleSpecifiers) && slices.EqualFunc(file1.ModuleAugmentations, file2.ModuleAugmentations, equalModuleAugmentationNames) && slices.Equal(file1.AmbientModuleNames, file2.AmbientModuleNames) && From d0a1ad85cf2675191fd854efbd30fef1e926e3d0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 19 May 2026 21:11:55 +0000 Subject: [PATCH 3/7] Strengthen helper update regression Co-authored-by: DanielRosenwasser <972891+DanielRosenwasser@users.noreply.github.com> --- internal/checker/checker_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/checker/checker_test.go b/internal/checker/checker_test.go index 74c44cdca81..b28f0f31116 100644 --- a/internal/checker/checker_test.go +++ b/internal/checker/checker_test.go @@ -107,8 +107,10 @@ export class C {}` "/tsconfig.json": config, }, false /*useCaseSensitiveFileNames*/)) updatedHost := compiler.NewCompilerHost("/", updatedFS, bundled.LibPath(), nil, nil) - updatedProgram, _ := p.UpdateProgram(path, updatedHost, nil) + updatedProgram, reused := p.UpdateProgram(path, updatedHost, nil) + assert.Assert(t, !reused, "Expected module indicator change to rebuild program state") updatedFile := updatedProgram.GetSourceFile("/foo.ts") + assert.Assert(t, updatedProgram.GetImportHelpersImportSpecifier(updatedFile.Path()) != nil, "Expected rebuilt program to synthesize tslib import") diagnostics := updatedProgram.GetSemanticDiagnostics(t.Context(), updatedFile) assert.Equal(t, len(diagnostics), 0, "Expected no semantic diagnostics") } From 7f7ac86aa9937bf3a4977d489c09cbcb1580a825 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 19 May 2026 21:12:38 +0000 Subject: [PATCH 4/7] Clean up helper regression test Co-authored-by: DanielRosenwasser <972891+DanielRosenwasser@users.noreply.github.com> --- internal/checker/checker_test.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/internal/checker/checker_test.go b/internal/checker/checker_test.go index b28f0f31116..b59ef02a526 100644 --- a/internal/checker/checker_test.go +++ b/internal/checker/checker_test.go @@ -9,7 +9,6 @@ import ( "github.com/microsoft/typescript-go/internal/compiler" "github.com/microsoft/typescript-go/internal/core" "github.com/microsoft/typescript-go/internal/repo" - "github.com/microsoft/typescript-go/internal/testutil" "github.com/microsoft/typescript-go/internal/tsoptions" "github.com/microsoft/typescript-go/internal/tspath" "github.com/microsoft/typescript-go/internal/vfs/osvfs" @@ -64,7 +63,6 @@ foo.bar;` func TestImportHelpersAfterProgramUpdateWithoutSyntheticImportSpecifier(t *testing.T) { t.Parallel() - defer testutil.RecoverAndFail(t, "Panic on import helpers after program update") const config = `{ "compilerOptions": { @@ -108,7 +106,7 @@ export class C {}` }, false /*useCaseSensitiveFileNames*/)) updatedHost := compiler.NewCompilerHost("/", updatedFS, bundled.LibPath(), nil, nil) updatedProgram, reused := p.UpdateProgram(path, updatedHost, nil) - assert.Assert(t, !reused, "Expected module indicator change to rebuild program state") + assert.Assert(t, !reused, "Expected module indicator change to rebuild stale state without a synthetic tslib import") updatedFile := updatedProgram.GetSourceFile("/foo.ts") assert.Assert(t, updatedProgram.GetImportHelpersImportSpecifier(updatedFile.Path()) != nil, "Expected rebuilt program to synthesize tslib import") diagnostics := updatedProgram.GetSemanticDiagnostics(t.Context(), updatedFile) From 4f19c00aab59f9bd15e88e4bd4844ff4726865bc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 19 May 2026 21:59:10 +0000 Subject: [PATCH 5/7] Move helper regression to fourslash Co-authored-by: DanielRosenwasser <972891+DanielRosenwasser@users.noreply.github.com> --- internal/checker/checker_test.go | 52 ------------------- ...sAfterScriptBecomesDecoratedModule_test.go | 45 ++++++++++++++++ 2 files changed, 45 insertions(+), 52 deletions(-) create mode 100644 internal/fourslash/tests/importHelpersAfterScriptBecomesDecoratedModule_test.go diff --git a/internal/checker/checker_test.go b/internal/checker/checker_test.go index b59ef02a526..00d93a65f2c 100644 --- a/internal/checker/checker_test.go +++ b/internal/checker/checker_test.go @@ -61,58 +61,6 @@ foo.bar;` } } -func TestImportHelpersAfterProgramUpdateWithoutSyntheticImportSpecifier(t *testing.T) { - t.Parallel() - - const config = `{ - "compilerOptions": { - "target": "es2015", - "module": "commonjs", - "experimentalDecorators": true, - "importHelpers": true - }, - "files": ["foo.ts"] - }` - const original = `declare function dec(value: Function): void; -class C {}` - const updated = `declare function dec(value: Function): void; -@dec -export class C {}` - const tslib = `export declare function __decorate(...args: any[]): any;` - - fs := bundled.WrapFS(vfstest.FromMap(map[string]string{ - "/foo.ts": original, - "/node_modules/tslib/package.json": `{"name":"tslib","typings":"tslib.d.ts"}`, - "/node_modules/tslib/tslib.d.ts": tslib, - "/node_modules/tslib/tslib.js": "", - "/tsconfig.json": config, - }, false /*useCaseSensitiveFileNames*/)) - host := compiler.NewCompilerHost("/", fs, bundled.LibPath(), nil, nil) - parsed, errors := tsoptions.GetParsedCommandLineOfConfigFile("/tsconfig.json", &core.CompilerOptions{}, nil, host, nil) - assert.Equal(t, len(errors), 0, "Expected no errors in parsed command line") - - p := compiler.NewProgram(compiler.ProgramOptions{ - Config: parsed, - Host: host, - }) - path := p.GetSourceFile("/foo.ts").Path() - - updatedFS := bundled.WrapFS(vfstest.FromMap(map[string]string{ - "/foo.ts": updated, - "/node_modules/tslib/package.json": `{"name":"tslib","typings":"tslib.d.ts"}`, - "/node_modules/tslib/tslib.d.ts": tslib, - "/node_modules/tslib/tslib.js": "", - "/tsconfig.json": config, - }, false /*useCaseSensitiveFileNames*/)) - updatedHost := compiler.NewCompilerHost("/", updatedFS, bundled.LibPath(), nil, nil) - updatedProgram, reused := p.UpdateProgram(path, updatedHost, nil) - assert.Assert(t, !reused, "Expected module indicator change to rebuild stale state without a synthetic tslib import") - updatedFile := updatedProgram.GetSourceFile("/foo.ts") - assert.Assert(t, updatedProgram.GetImportHelpersImportSpecifier(updatedFile.Path()) != nil, "Expected rebuilt program to synthesize tslib import") - diagnostics := updatedProgram.GetSemanticDiagnostics(t.Context(), updatedFile) - assert.Equal(t, len(diagnostics), 0, "Expected no semantic diagnostics") -} - func BenchmarkNewChecker(b *testing.B) { repo.SkipIfNoTypeScriptSubmodule(b) fs := osvfs.FS() diff --git a/internal/fourslash/tests/importHelpersAfterScriptBecomesDecoratedModule_test.go b/internal/fourslash/tests/importHelpersAfterScriptBecomesDecoratedModule_test.go new file mode 100644 index 00000000000..9a8e2d07b1a --- /dev/null +++ b/internal/fourslash/tests/importHelpersAfterScriptBecomesDecoratedModule_test.go @@ -0,0 +1,45 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportHelpersAfterScriptBecomesDecoratedModule(t *testing.T) { + t.Parallel() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + + const content = `// @Filename: /tsconfig.json +{ + "compilerOptions": { + "target": "es2015", + "module": "commonjs", + "experimentalDecorators": true, + "importHelpers": true + }, + "files": ["foo.ts"] +} + +// @Filename: /foo.ts +declare function dec(value: Function): void; +/*insert*/class C {} + +// @Filename: /node_modules/tslib/package.json +{ "name": "tslib", "typings": "tslib.d.ts" } + +// @Filename: /node_modules/tslib/tslib.d.ts +export declare function __decorate(...args: any[]): any; + +// @Filename: /node_modules/tslib/tslib.js +exports.__decorate = function () {}; +` + f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) + defer done() + + f.GoToFile(t, "/foo.ts") + f.VerifyNumberOfErrorsInCurrentFile(t, 0) + f.Replace(t, f.MarkerByName(t, "insert").Position, 0, "@dec\nexport ") + f.VerifyNumberOfErrorsInCurrentFile(t, 0) +} From 7a1766c9fc9723ff11064cb096acd6665da74ea9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 19 May 2026 21:59:52 +0000 Subject: [PATCH 6/7] Clarify fourslash helper edit Co-authored-by: DanielRosenwasser <972891+DanielRosenwasser@users.noreply.github.com> --- .../importHelpersAfterScriptBecomesDecoratedModule_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/fourslash/tests/importHelpersAfterScriptBecomesDecoratedModule_test.go b/internal/fourslash/tests/importHelpersAfterScriptBecomesDecoratedModule_test.go index 9a8e2d07b1a..f97c3372bce 100644 --- a/internal/fourslash/tests/importHelpersAfterScriptBecomesDecoratedModule_test.go +++ b/internal/fourslash/tests/importHelpersAfterScriptBecomesDecoratedModule_test.go @@ -40,6 +40,7 @@ exports.__decorate = function () {}; f.GoToFile(t, "/foo.ts") f.VerifyNumberOfErrorsInCurrentFile(t, 0) - f.Replace(t, f.MarkerByName(t, "insert").Position, 0, "@dec\nexport ") + f.Replace(t, f.MarkerByName(t, "insert").Position, 0, `@dec +export `) f.VerifyNumberOfErrorsInCurrentFile(t, 0) } From f732c93749b7b7884fd170fc328efb62857d47d3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 19 May 2026 22:01:17 +0000 Subject: [PATCH 7/7] Document helper resolution assertion Co-authored-by: DanielRosenwasser <972891+DanielRosenwasser@users.noreply.github.com> --- .../tests/importHelpersAfterScriptBecomesDecoratedModule_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/fourslash/tests/importHelpersAfterScriptBecomesDecoratedModule_test.go b/internal/fourslash/tests/importHelpersAfterScriptBecomesDecoratedModule_test.go index f97c3372bce..1928537f424 100644 --- a/internal/fourslash/tests/importHelpersAfterScriptBecomesDecoratedModule_test.go +++ b/internal/fourslash/tests/importHelpersAfterScriptBecomesDecoratedModule_test.go @@ -42,5 +42,6 @@ exports.__decorate = function () {}; f.VerifyNumberOfErrorsInCurrentFile(t, 0) f.Replace(t, f.MarkerByName(t, "insert").Position, 0, `@dec export `) + // The second diagnostics request forces external helper resolution after the edit. f.VerifyNumberOfErrorsInCurrentFile(t, 0) }