From e6a6d78758d2d7577675ada3c6d0f358a21df55d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sun, 27 Sep 2026 12:18:25 +0200 Subject: [PATCH 1/6] Always track flaky diagnostics in fourslash; add failing 7016 tests Fourslash now initializes the server with trackFlakyDiagnostics set to panic, so every textDocument/diagnostic request also emits the program and fails if the diagnostics differ before and after emit. This complements the compiler harness's pre/post-emit check, which compares separate programs by diagnostic count only and doesn't catch emit adding a diagnostic to an already-checked program. Add fourslash tests where declaration emit adds a "Could not find a declaration file for module" (7016) suggestion for an untyped import whose error was dropped by checking (plain JS file, @ts-expect-error, and an ES module under NodeNext). --- tsc/internal/fourslash/fourslash.go | 3 +++ .../noFlakyDiagnosticsUntypedModule1_test.go | 26 +++++++++++++++++++ .../noFlakyDiagnosticsUntypedModule2_test.go | 25 ++++++++++++++++++ .../noFlakyDiagnosticsUntypedModule3_test.go | 26 +++++++++++++++++++ 4 files changed, 80 insertions(+) create mode 100644 tsc/internal/fourslash/tests/noFlakyDiagnosticsUntypedModule1_test.go create mode 100644 tsc/internal/fourslash/tests/noFlakyDiagnosticsUntypedModule2_test.go create mode 100644 tsc/internal/fourslash/tests/noFlakyDiagnosticsUntypedModule3_test.go diff --git a/tsc/internal/fourslash/fourslash.go b/tsc/internal/fourslash/fourslash.go index de4ae802e3f84..f44e20f05bc1a 100644 --- a/tsc/internal/fourslash/fourslash.go +++ b/tsc/internal/fourslash/fourslash.go @@ -368,6 +368,9 @@ const showCodeLensLocationsCommandName = "typescript.showCodeLensLocations" func (f *FourslashTest) initialize(t *testing.T, capabilities *lsproto.ClientCapabilities, runExternalCode bool) { initializationOptions := &lsproto.InitializationOptions{ CodeLensShowLocationsCommandName: new(showCodeLensLocationsCommandName), + // Make every textDocument/diagnostic request also emit the program and fail if the + // diagnostics differ before and after emit, e.g. because the emit resolver added some. + TrackFlakyDiagnostics: new(lsproto.DiagnosticFlakeLogLevelPanic), } if runExternalCode { initializationOptions.RunExternalCode = new(true) diff --git a/tsc/internal/fourslash/tests/noFlakyDiagnosticsUntypedModule1_test.go b/tsc/internal/fourslash/tests/noFlakyDiagnosticsUntypedModule1_test.go new file mode 100644 index 0000000000000..3d92d1a7886fb --- /dev/null +++ b/tsc/internal/fourslash/tests/noFlakyDiagnosticsUntypedModule1_test.go @@ -0,0 +1,26 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/TypeScript/tsc/internal/fourslash" + "github.com/microsoft/TypeScript/tsc/internal/testutil" +) + +func TestNoFlakyDiagnosticsUntypedModule1(t *testing.T) { + t.Parallel() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @allowJs: true +// @noImplicitAny: true +// @declaration: true +// @Filename: /node_modules/untyped/index.js +// @noOpen: true +exports.x = 1; +// @Filename: /index.js +import { x } from "untyped"; +x;` + f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) + defer done() + f.GoToFile(t, "/index.js") + f.VerifySuggestionDiagnostics(t, nil) +} diff --git a/tsc/internal/fourslash/tests/noFlakyDiagnosticsUntypedModule2_test.go b/tsc/internal/fourslash/tests/noFlakyDiagnosticsUntypedModule2_test.go new file mode 100644 index 0000000000000..f0fb4d52fe8f5 --- /dev/null +++ b/tsc/internal/fourslash/tests/noFlakyDiagnosticsUntypedModule2_test.go @@ -0,0 +1,25 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/TypeScript/tsc/internal/fourslash" + "github.com/microsoft/TypeScript/tsc/internal/testutil" +) + +func TestNoFlakyDiagnosticsUntypedModule2(t *testing.T) { + t.Parallel() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @noImplicitAny: true +// @declaration: true +// @Filename: /untyped.js +exports.x = 1; +// @Filename: /index.ts +// @ts-expect-error +import { x } from "./untyped"; +x;` + f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) + defer done() + f.GoToFile(t, "/index.ts") + f.VerifySuggestionDiagnostics(t, nil) +} diff --git a/tsc/internal/fourslash/tests/noFlakyDiagnosticsUntypedModule3_test.go b/tsc/internal/fourslash/tests/noFlakyDiagnosticsUntypedModule3_test.go new file mode 100644 index 0000000000000..7c9e3afc55294 --- /dev/null +++ b/tsc/internal/fourslash/tests/noFlakyDiagnosticsUntypedModule3_test.go @@ -0,0 +1,26 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/TypeScript/tsc/internal/fourslash" + "github.com/microsoft/TypeScript/tsc/internal/testutil" +) + +func TestNoFlakyDiagnosticsUntypedModule3(t *testing.T) { + t.Parallel() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @module: nodenext +// @noImplicitAny: true +// @declaration: true +// @Filename: /untyped.mjs +export const x = 1; +// @Filename: /index.mts +// @ts-expect-error +import { x } from "./untyped.mjs"; +x;` + f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) + defer done() + f.GoToFile(t, "/index.mts") + f.VerifySuggestionDiagnostics(t, nil) +} From be1f21844e68e31791dd885d3e54934c1c7712d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sun, 27 Sep 2026 12:18:25 +0200 Subject: [PATCH 2/6] Don't report module resolution diagnostics from getExternalModuleFileFromDeclaration getExternalModuleFileFromDeclaration is only used by emit (IsImportRequiredByAugmentation, module transforms) and by type-node reuse in the node builder. It resolved with the specifier as the error node, so for an untyped JS module it re-ran errorOnImplicitAnyModule with moduleNotFoundError == nil and recorded 7016 as a *suggestion*. Checking had already reported 7016 as an error, which was then dropped (plain JS file or @ts-expect-error), but suggestions bypass both filters, so the diagnostic appeared only after emit and tripped the LSP's flaky-diagnostic tracking. Resolve with ignoreErrors so these emit-time queries are free of diagnostic side effects. Fixes #64458 --- tsc/internal/checker/checker.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 845f62365bb84..14bec59efdc95 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -15379,7 +15379,10 @@ func (c *Checker) getExternalModuleFileFromDeclaration(declaration *ast.Node) *a if ast.HasImportAttributes(declaration) { importAttributesType = c.getTypeFromImportAttributes(ast.GetImportAttributes(declaration)) } - moduleSymbol := c.resolveExternalModuleNameWorker(specifier, specifier /*moduleNotFoundError*/, nil, false, false, importAttributesType) // TODO: GH#18217 + // This is only used by emit and type printing, after checking has already reported any + // resolution errors for this specifier. Resolve with ignoreErrors so that these queries + // don't add new diagnostics (e.g. an implicit-any-module suggestion) as a side effect. + moduleSymbol := c.resolveExternalModuleNameWorker(specifier, specifier, nil /*moduleNotFoundError*/, true /*ignoreErrors*/, false /*isForAugmentation*/, importAttributesType) if moduleSymbol == nil { return nil } From dae525b0b940c95fa509c0d1fe849b8f1946886f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Tue, 29 Sep 2026 18:03:29 +0200 Subject: [PATCH 3/6] Add compiler tests for emit-time untyped module suggestions and collect pre-emit suggestions before declaration diagnostics --- .../testutil/harnessutil/harnessutil.go | 6 +++--- .../noFlakyDiagnosticsUntypedModule1.js | 16 ++++++++++++++++ .../noFlakyDiagnosticsUntypedModule1.symbols | 9 +++++++++ .../noFlakyDiagnosticsUntypedModule1.types | 9 +++++++++ .../noFlakyDiagnosticsUntypedModule2.js | 18 ++++++++++++++++++ .../noFlakyDiagnosticsUntypedModule2.symbols | 10 ++++++++++ .../noFlakyDiagnosticsUntypedModule2.types | 10 ++++++++++ .../noFlakyDiagnosticsUntypedModule3.js | 18 ++++++++++++++++++ .../noFlakyDiagnosticsUntypedModule3.symbols | 10 ++++++++++ .../noFlakyDiagnosticsUntypedModule3.types | 10 ++++++++++ .../noFlakyDiagnosticsUntypedModule1.ts | 17 +++++++++++++++++ .../noFlakyDiagnosticsUntypedModule2.ts | 15 +++++++++++++++ .../noFlakyDiagnosticsUntypedModule3.ts | 16 ++++++++++++++++ 13 files changed, 161 insertions(+), 3 deletions(-) create mode 100644 tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsUntypedModule1.js create mode 100644 tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsUntypedModule1.symbols create mode 100644 tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsUntypedModule1.types create mode 100644 tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsUntypedModule2.js create mode 100644 tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsUntypedModule2.symbols create mode 100644 tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsUntypedModule2.types create mode 100644 tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsUntypedModule3.js create mode 100644 tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsUntypedModule3.symbols create mode 100644 tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsUntypedModule3.types create mode 100644 tsc/testdata/tests/cases/compiler/noFlakyDiagnosticsUntypedModule1.ts create mode 100644 tsc/testdata/tests/cases/compiler/noFlakyDiagnosticsUntypedModule2.ts create mode 100644 tsc/testdata/tests/cases/compiler/noFlakyDiagnosticsUntypedModule3.ts diff --git a/tsc/internal/testutil/harnessutil/harnessutil.go b/tsc/internal/testutil/harnessutil/harnessutil.go index 0b46a9a7a7cd7..1de43adc3761a 100644 --- a/tsc/internal/testutil/harnessutil/harnessutil.go +++ b/tsc/internal/testutil/harnessutil/harnessutil.go @@ -663,12 +663,12 @@ func compileFilesWithHost( preErrors = append(preErrors, preProgram.GetSyntacticDiagnostics(ctx, nil)...) preErrors = append(preErrors, preProgram.GetSemanticDiagnostics(ctx, nil)...) preErrors = append(preErrors, preProgram.GetGlobalDiagnostics(ctx)...) - if preProgram.Options().GetEmitDeclarations() { - preErrors = append(preErrors, preProgram.GetDeclarationDiagnostics(ctx, nil)...) - } if harnessOptions.CaptureSuggestions { preErrors = append(preErrors, preProgram.GetSuggestionDiagnostics(ctx, nil)...) } + if preProgram.Options().GetEmitDeclarations() { + preErrors = append(preErrors, preProgram.GetDeclarationDiagnostics(ctx, nil)...) + } preErrors = compiler.SortAndDeduplicateDiagnostics(preErrors) postProgram := createProgram(host, config) diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsUntypedModule1.js b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsUntypedModule1.js new file mode 100644 index 0000000000000..127d8e7dcb788 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsUntypedModule1.js @@ -0,0 +1,16 @@ +//// [tests/cases/compiler/noFlakyDiagnosticsUntypedModule1.ts] //// + +//// [index.js] +exports.x = 1; +//// [index.js] +import { x } from "untyped"; +x; + + +//// [index.js] +import { x } from "untyped"; +x; + + +//// [index.d.ts] +export {}; diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsUntypedModule1.symbols b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsUntypedModule1.symbols new file mode 100644 index 0000000000000..a6703bcae24b5 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsUntypedModule1.symbols @@ -0,0 +1,9 @@ +//// [tests/cases/compiler/noFlakyDiagnosticsUntypedModule1.ts] //// + +=== /index.js === +import { x } from "untyped"; +>x : Symbol(x, Decl(index.js, 0, 8)) + +x; +>x : Symbol(x, Decl(index.js, 0, 8)) + diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsUntypedModule1.types b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsUntypedModule1.types new file mode 100644 index 0000000000000..5282c8905ece1 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsUntypedModule1.types @@ -0,0 +1,9 @@ +//// [tests/cases/compiler/noFlakyDiagnosticsUntypedModule1.ts] //// + +=== /index.js === +import { x } from "untyped"; +>x : any + +x; +>x : error + diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsUntypedModule2.js b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsUntypedModule2.js new file mode 100644 index 0000000000000..6b5a480bdb37d --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsUntypedModule2.js @@ -0,0 +1,18 @@ +//// [tests/cases/compiler/noFlakyDiagnosticsUntypedModule2.ts] //// + +//// [untyped.js] +exports.x = 1; +//// [index.ts] +// @ts-expect-error +import { x } from "./untyped"; +x; + + +//// [index.js] +// @ts-expect-error +import { x } from "./untyped"; +x; + + +//// [index.d.ts] +export {}; diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsUntypedModule2.symbols b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsUntypedModule2.symbols new file mode 100644 index 0000000000000..66b7fa45f7d77 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsUntypedModule2.symbols @@ -0,0 +1,10 @@ +//// [tests/cases/compiler/noFlakyDiagnosticsUntypedModule2.ts] //// + +=== /index.ts === +// @ts-expect-error +import { x } from "./untyped"; +>x : Symbol(x, Decl(index.ts, 1, 8)) + +x; +>x : Symbol(x, Decl(index.ts, 1, 8)) + diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsUntypedModule2.types b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsUntypedModule2.types new file mode 100644 index 0000000000000..7f2feb1e7fd4f --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsUntypedModule2.types @@ -0,0 +1,10 @@ +//// [tests/cases/compiler/noFlakyDiagnosticsUntypedModule2.ts] //// + +=== /index.ts === +// @ts-expect-error +import { x } from "./untyped"; +>x : any + +x; +>x : error + diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsUntypedModule3.js b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsUntypedModule3.js new file mode 100644 index 0000000000000..916b339a214ce --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsUntypedModule3.js @@ -0,0 +1,18 @@ +//// [tests/cases/compiler/noFlakyDiagnosticsUntypedModule3.ts] //// + +//// [untyped.mjs] +export const x = 1; +//// [index.mts] +// @ts-expect-error +import { x } from "./untyped.mjs"; +x; + + +//// [index.mjs] +// @ts-expect-error +import { x } from "./untyped.mjs"; +x; + + +//// [index.d.mts] +export {}; diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsUntypedModule3.symbols b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsUntypedModule3.symbols new file mode 100644 index 0000000000000..9e2f7c5506c1d --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsUntypedModule3.symbols @@ -0,0 +1,10 @@ +//// [tests/cases/compiler/noFlakyDiagnosticsUntypedModule3.ts] //// + +=== /index.mts === +// @ts-expect-error +import { x } from "./untyped.mjs"; +>x : Symbol(x, Decl(index.mts, 1, 8)) + +x; +>x : Symbol(x, Decl(index.mts, 1, 8)) + diff --git a/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsUntypedModule3.types b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsUntypedModule3.types new file mode 100644 index 0000000000000..bfbd81030b2a0 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/noFlakyDiagnosticsUntypedModule3.types @@ -0,0 +1,10 @@ +//// [tests/cases/compiler/noFlakyDiagnosticsUntypedModule3.ts] //// + +=== /index.mts === +// @ts-expect-error +import { x } from "./untyped.mjs"; +>x : any + +x; +>x : error + diff --git a/tsc/testdata/tests/cases/compiler/noFlakyDiagnosticsUntypedModule1.ts b/tsc/testdata/tests/cases/compiler/noFlakyDiagnosticsUntypedModule1.ts new file mode 100644 index 0000000000000..628b45241a2dd --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/noFlakyDiagnosticsUntypedModule1.ts @@ -0,0 +1,17 @@ +// @captureSuggestions: true +// @Filename: /tsconfig.json +{ + "compilerOptions": { + "allowJs": true, + "noImplicitAny": true, + "declaration": true, + "outDir": "/out", + "rootDir": "/" + }, + "files": ["/index.js"] +} +// @Filename: /node_modules/untyped/index.js +exports.x = 1; +// @Filename: /index.js +import { x } from "untyped"; +x; diff --git a/tsc/testdata/tests/cases/compiler/noFlakyDiagnosticsUntypedModule2.ts b/tsc/testdata/tests/cases/compiler/noFlakyDiagnosticsUntypedModule2.ts new file mode 100644 index 0000000000000..1695a99383e61 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/noFlakyDiagnosticsUntypedModule2.ts @@ -0,0 +1,15 @@ +// @captureSuggestions: true +// @Filename: /tsconfig.json +{ + "compilerOptions": { + "noImplicitAny": true, + "declaration": true + }, + "files": ["/index.ts"] +} +// @Filename: /untyped.js +exports.x = 1; +// @Filename: /index.ts +// @ts-expect-error +import { x } from "./untyped"; +x; diff --git a/tsc/testdata/tests/cases/compiler/noFlakyDiagnosticsUntypedModule3.ts b/tsc/testdata/tests/cases/compiler/noFlakyDiagnosticsUntypedModule3.ts new file mode 100644 index 0000000000000..6c35fba2d2930 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/noFlakyDiagnosticsUntypedModule3.ts @@ -0,0 +1,16 @@ +// @captureSuggestions: true +// @Filename: /tsconfig.json +{ + "compilerOptions": { + "module": "nodenext", + "noImplicitAny": true, + "declaration": true + }, + "files": ["/index.mts"] +} +// @Filename: /untyped.mjs +export const x = 1; +// @Filename: /index.mts +// @ts-expect-error +import { x } from "./untyped.mjs"; +x; From d095051a86f6ca65c1e9ffb8af762f0c450aa214 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Tue, 29 Sep 2026 19:22:04 +0200 Subject: [PATCH 4/6] Use noImplicitReferences instead of tsconfig in untyped module suggestion tests --- .../compiler/noFlakyDiagnosticsUntypedModule1.ts | 16 +++++----------- .../compiler/noFlakyDiagnosticsUntypedModule2.ts | 11 +++-------- .../compiler/noFlakyDiagnosticsUntypedModule3.ts | 13 ++++--------- 3 files changed, 12 insertions(+), 28 deletions(-) diff --git a/tsc/testdata/tests/cases/compiler/noFlakyDiagnosticsUntypedModule1.ts b/tsc/testdata/tests/cases/compiler/noFlakyDiagnosticsUntypedModule1.ts index 628b45241a2dd..297be38455c66 100644 --- a/tsc/testdata/tests/cases/compiler/noFlakyDiagnosticsUntypedModule1.ts +++ b/tsc/testdata/tests/cases/compiler/noFlakyDiagnosticsUntypedModule1.ts @@ -1,15 +1,9 @@ +// @allowJs: true +// @noImplicitAny: true +// @declaration: true +// @outDir: /out +// @noImplicitReferences: true // @captureSuggestions: true -// @Filename: /tsconfig.json -{ - "compilerOptions": { - "allowJs": true, - "noImplicitAny": true, - "declaration": true, - "outDir": "/out", - "rootDir": "/" - }, - "files": ["/index.js"] -} // @Filename: /node_modules/untyped/index.js exports.x = 1; // @Filename: /index.js diff --git a/tsc/testdata/tests/cases/compiler/noFlakyDiagnosticsUntypedModule2.ts b/tsc/testdata/tests/cases/compiler/noFlakyDiagnosticsUntypedModule2.ts index 1695a99383e61..a2833d3af1087 100644 --- a/tsc/testdata/tests/cases/compiler/noFlakyDiagnosticsUntypedModule2.ts +++ b/tsc/testdata/tests/cases/compiler/noFlakyDiagnosticsUntypedModule2.ts @@ -1,12 +1,7 @@ +// @noImplicitAny: true +// @declaration: true +// @noImplicitReferences: true // @captureSuggestions: true -// @Filename: /tsconfig.json -{ - "compilerOptions": { - "noImplicitAny": true, - "declaration": true - }, - "files": ["/index.ts"] -} // @Filename: /untyped.js exports.x = 1; // @Filename: /index.ts diff --git a/tsc/testdata/tests/cases/compiler/noFlakyDiagnosticsUntypedModule3.ts b/tsc/testdata/tests/cases/compiler/noFlakyDiagnosticsUntypedModule3.ts index 6c35fba2d2930..445e5b38a84a5 100644 --- a/tsc/testdata/tests/cases/compiler/noFlakyDiagnosticsUntypedModule3.ts +++ b/tsc/testdata/tests/cases/compiler/noFlakyDiagnosticsUntypedModule3.ts @@ -1,13 +1,8 @@ +// @module: nodenext +// @noImplicitAny: true +// @declaration: true +// @noImplicitReferences: true // @captureSuggestions: true -// @Filename: /tsconfig.json -{ - "compilerOptions": { - "module": "nodenext", - "noImplicitAny": true, - "declaration": true - }, - "files": ["/index.mts"] -} // @Filename: /untyped.mjs export const x = 1; // @Filename: /index.mts From 15553abe9c8b588d028f0d9559666ab319931ec8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Tue, 29 Sep 2026 19:28:18 +0200 Subject: [PATCH 5/6] Make flaky diagnostic tracking an opt-in fourslash option --- tsc/internal/fourslash/fourslash.go | 15 ++++++++------- .../noFlakyDiagnosticsUntypedModule1_test.go | 5 ++++- .../noFlakyDiagnosticsUntypedModule2_test.go | 5 ++++- .../noFlakyDiagnosticsUntypedModule3_test.go | 5 ++++- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/tsc/internal/fourslash/fourslash.go b/tsc/internal/fourslash/fourslash.go index f44e20f05bc1a..8aac0ca986922 100644 --- a/tsc/internal/fourslash/fourslash.go +++ b/tsc/internal/fourslash/fourslash.go @@ -166,6 +166,9 @@ type FourslashOptions struct { Capabilities *lsproto.ClientCapabilities ContentMapperSpawner contentmapper.Spawner RunExternalCode bool + // Makes every textDocument/diagnostic request also emit the program and compare + // the diagnostics before and after emit, e.g. to catch ones added by the emit resolver. + TrackFlakyDiagnostics *lsproto.DiagnosticFlakeLogLevel } func NewFourslashWithOptions(t *testing.T, content string, options *FourslashOptions) (*FourslashTest, func()) { @@ -260,7 +263,7 @@ func newFourslash(t *testing.T, content string, options *FourslashOptions, testP // !!! temporary; remove when we have `handleDidChangeConfiguration`/implicit project config support // !!! replace with a proper request *after initialize* client.SetCompilerOptionsForInferredProjects(compilerOptions) - f.initialize(t, options.Capabilities, options.RunExternalCode) + f.initialize(t, options) if testData.isStateBaseliningEnabled() { // Single baseline, so initialize project state baseline too @@ -365,14 +368,12 @@ func getBaseFileNameFromTest(t *testing.T) string { const showCodeLensLocationsCommandName = "typescript.showCodeLensLocations" -func (f *FourslashTest) initialize(t *testing.T, capabilities *lsproto.ClientCapabilities, runExternalCode bool) { +func (f *FourslashTest) initialize(t *testing.T, options *FourslashOptions) { initializationOptions := &lsproto.InitializationOptions{ CodeLensShowLocationsCommandName: new(showCodeLensLocationsCommandName), - // Make every textDocument/diagnostic request also emit the program and fail if the - // diagnostics differ before and after emit, e.g. because the emit resolver added some. - TrackFlakyDiagnostics: new(lsproto.DiagnosticFlakeLogLevelPanic), + TrackFlakyDiagnostics: options.TrackFlakyDiagnostics, } - if runExternalCode { + if options.RunExternalCode { initializationOptions.RunExternalCode = new(true) } params := &lsproto.InitializeParams{ @@ -381,7 +382,7 @@ func (f *FourslashTest) initialize(t *testing.T, capabilities *lsproto.ClientCap InitializationOptions: initializationOptions, }, } - params.Capabilities = getCapabilitiesWithDefaults(capabilities) + params.Capabilities = getCapabilitiesWithDefaults(options.Capabilities) f.capabilities = params.Capabilities resp, _, ok := f.client.SendRequest(t, lsproto.InitializeInfo, params) if !ok { diff --git a/tsc/internal/fourslash/tests/noFlakyDiagnosticsUntypedModule1_test.go b/tsc/internal/fourslash/tests/noFlakyDiagnosticsUntypedModule1_test.go index 3d92d1a7886fb..a7d368634ae99 100644 --- a/tsc/internal/fourslash/tests/noFlakyDiagnosticsUntypedModule1_test.go +++ b/tsc/internal/fourslash/tests/noFlakyDiagnosticsUntypedModule1_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/microsoft/TypeScript/tsc/internal/fourslash" + "github.com/microsoft/TypeScript/tsc/internal/lsp/lsproto" "github.com/microsoft/TypeScript/tsc/internal/testutil" ) @@ -19,7 +20,9 @@ exports.x = 1; // @Filename: /index.js import { x } from "untyped"; x;` - f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f, done := fourslash.NewFourslashWithOptions(t, content, &fourslash.FourslashOptions{ + TrackFlakyDiagnostics: new(lsproto.DiagnosticFlakeLogLevelPanic), + }) defer done() f.GoToFile(t, "/index.js") f.VerifySuggestionDiagnostics(t, nil) diff --git a/tsc/internal/fourslash/tests/noFlakyDiagnosticsUntypedModule2_test.go b/tsc/internal/fourslash/tests/noFlakyDiagnosticsUntypedModule2_test.go index f0fb4d52fe8f5..7130788e1a0a3 100644 --- a/tsc/internal/fourslash/tests/noFlakyDiagnosticsUntypedModule2_test.go +++ b/tsc/internal/fourslash/tests/noFlakyDiagnosticsUntypedModule2_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/microsoft/TypeScript/tsc/internal/fourslash" + "github.com/microsoft/TypeScript/tsc/internal/lsp/lsproto" "github.com/microsoft/TypeScript/tsc/internal/testutil" ) @@ -18,7 +19,9 @@ exports.x = 1; // @ts-expect-error import { x } from "./untyped"; x;` - f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f, done := fourslash.NewFourslashWithOptions(t, content, &fourslash.FourslashOptions{ + TrackFlakyDiagnostics: new(lsproto.DiagnosticFlakeLogLevelPanic), + }) defer done() f.GoToFile(t, "/index.ts") f.VerifySuggestionDiagnostics(t, nil) diff --git a/tsc/internal/fourslash/tests/noFlakyDiagnosticsUntypedModule3_test.go b/tsc/internal/fourslash/tests/noFlakyDiagnosticsUntypedModule3_test.go index 7c9e3afc55294..87190e022b598 100644 --- a/tsc/internal/fourslash/tests/noFlakyDiagnosticsUntypedModule3_test.go +++ b/tsc/internal/fourslash/tests/noFlakyDiagnosticsUntypedModule3_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/microsoft/TypeScript/tsc/internal/fourslash" + "github.com/microsoft/TypeScript/tsc/internal/lsp/lsproto" "github.com/microsoft/TypeScript/tsc/internal/testutil" ) @@ -19,7 +20,9 @@ export const x = 1; // @ts-expect-error import { x } from "./untyped.mjs"; x;` - f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f, done := fourslash.NewFourslashWithOptions(t, content, &fourslash.FourslashOptions{ + TrackFlakyDiagnostics: new(lsproto.DiagnosticFlakeLogLevelPanic), + }) defer done() f.GoToFile(t, "/index.mts") f.VerifySuggestionDiagnostics(t, nil) From 9280b068d8e868b9b6c3f4807fe2596d10aba7d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Tue, 29 Sep 2026 19:30:37 +0200 Subject: [PATCH 6/6] Remove fourslash untyped module suggestion tests covered by compiler tests --- .../noFlakyDiagnosticsUntypedModule1_test.go | 29 ------------------- .../noFlakyDiagnosticsUntypedModule2_test.go | 28 ------------------ .../noFlakyDiagnosticsUntypedModule3_test.go | 29 ------------------- 3 files changed, 86 deletions(-) delete mode 100644 tsc/internal/fourslash/tests/noFlakyDiagnosticsUntypedModule1_test.go delete mode 100644 tsc/internal/fourslash/tests/noFlakyDiagnosticsUntypedModule2_test.go delete mode 100644 tsc/internal/fourslash/tests/noFlakyDiagnosticsUntypedModule3_test.go diff --git a/tsc/internal/fourslash/tests/noFlakyDiagnosticsUntypedModule1_test.go b/tsc/internal/fourslash/tests/noFlakyDiagnosticsUntypedModule1_test.go deleted file mode 100644 index a7d368634ae99..0000000000000 --- a/tsc/internal/fourslash/tests/noFlakyDiagnosticsUntypedModule1_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package fourslash_test - -import ( - "testing" - - "github.com/microsoft/TypeScript/tsc/internal/fourslash" - "github.com/microsoft/TypeScript/tsc/internal/lsp/lsproto" - "github.com/microsoft/TypeScript/tsc/internal/testutil" -) - -func TestNoFlakyDiagnosticsUntypedModule1(t *testing.T) { - t.Parallel() - defer testutil.RecoverAndFail(t, "Panic on fourslash test") - const content = `// @allowJs: true -// @noImplicitAny: true -// @declaration: true -// @Filename: /node_modules/untyped/index.js -// @noOpen: true -exports.x = 1; -// @Filename: /index.js -import { x } from "untyped"; -x;` - f, done := fourslash.NewFourslashWithOptions(t, content, &fourslash.FourslashOptions{ - TrackFlakyDiagnostics: new(lsproto.DiagnosticFlakeLogLevelPanic), - }) - defer done() - f.GoToFile(t, "/index.js") - f.VerifySuggestionDiagnostics(t, nil) -} diff --git a/tsc/internal/fourslash/tests/noFlakyDiagnosticsUntypedModule2_test.go b/tsc/internal/fourslash/tests/noFlakyDiagnosticsUntypedModule2_test.go deleted file mode 100644 index 7130788e1a0a3..0000000000000 --- a/tsc/internal/fourslash/tests/noFlakyDiagnosticsUntypedModule2_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package fourslash_test - -import ( - "testing" - - "github.com/microsoft/TypeScript/tsc/internal/fourslash" - "github.com/microsoft/TypeScript/tsc/internal/lsp/lsproto" - "github.com/microsoft/TypeScript/tsc/internal/testutil" -) - -func TestNoFlakyDiagnosticsUntypedModule2(t *testing.T) { - t.Parallel() - defer testutil.RecoverAndFail(t, "Panic on fourslash test") - const content = `// @noImplicitAny: true -// @declaration: true -// @Filename: /untyped.js -exports.x = 1; -// @Filename: /index.ts -// @ts-expect-error -import { x } from "./untyped"; -x;` - f, done := fourslash.NewFourslashWithOptions(t, content, &fourslash.FourslashOptions{ - TrackFlakyDiagnostics: new(lsproto.DiagnosticFlakeLogLevelPanic), - }) - defer done() - f.GoToFile(t, "/index.ts") - f.VerifySuggestionDiagnostics(t, nil) -} diff --git a/tsc/internal/fourslash/tests/noFlakyDiagnosticsUntypedModule3_test.go b/tsc/internal/fourslash/tests/noFlakyDiagnosticsUntypedModule3_test.go deleted file mode 100644 index 87190e022b598..0000000000000 --- a/tsc/internal/fourslash/tests/noFlakyDiagnosticsUntypedModule3_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package fourslash_test - -import ( - "testing" - - "github.com/microsoft/TypeScript/tsc/internal/fourslash" - "github.com/microsoft/TypeScript/tsc/internal/lsp/lsproto" - "github.com/microsoft/TypeScript/tsc/internal/testutil" -) - -func TestNoFlakyDiagnosticsUntypedModule3(t *testing.T) { - t.Parallel() - defer testutil.RecoverAndFail(t, "Panic on fourslash test") - const content = `// @module: nodenext -// @noImplicitAny: true -// @declaration: true -// @Filename: /untyped.mjs -export const x = 1; -// @Filename: /index.mts -// @ts-expect-error -import { x } from "./untyped.mjs"; -x;` - f, done := fourslash.NewFourslashWithOptions(t, content, &fourslash.FourslashOptions{ - TrackFlakyDiagnostics: new(lsproto.DiagnosticFlakeLogLevelPanic), - }) - defer done() - f.GoToFile(t, "/index.mts") - f.VerifySuggestionDiagnostics(t, nil) -}