From 6522ecbe0016c2daaf4d6cbc2267ba45ed788886 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 24 Sep 2026 18:15:11 +0000 Subject: [PATCH 1/3] Initial plan From 2f0bf5a8456899e2122a1447a143eb50e79fcb46 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 24 Sep 2026 18:25:41 +0000 Subject: [PATCH 2/3] Deduplicate content mapper inlay hints Co-authored-by: andrewbranch <3277153+andrewbranch@users.noreply.github.com> --- .../tests/contentMapperInlayHints_test.go | 27 ++++++ tsc/internal/ls/inlay_hints.go | 13 ++- .../contentMapperInlayHints.baseline | 96 +++++++++++++++++++ 3 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 tsc/internal/fourslash/tests/contentMapperInlayHints_test.go create mode 100644 tsc/testdata/baselines/reference/fourslash/inlayHints/contentMapperInlayHints.baseline diff --git a/tsc/internal/fourslash/tests/contentMapperInlayHints_test.go b/tsc/internal/fourslash/tests/contentMapperInlayHints_test.go new file mode 100644 index 0000000000000..63607c844cc15 --- /dev/null +++ b/tsc/internal/fourslash/tests/contentMapperInlayHints_test.go @@ -0,0 +1,27 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/TypeScript/tsc/internal/ls/lsutil" + "github.com/microsoft/TypeScript/tsc/internal/testutil" + "github.com/microsoft/TypeScript/tsc/internal/testutil/contentmappertest" +) + +func TestContentMapperInlayHints(t *testing.T) { + t.Parallel() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + // original: (+ 1 2 "oops") + // virtual: add(1, 2, "oops"); + // [-) [) [) [----) mapped spans + f, done := newContentMapperFourslash(t, `// @Filename: /app.lisp +(+ 1 2 "oops") + +// @Filename: /main.ts +declare function add(a: number, b: number, c: string): number; +`, contentmappertest.LispMapper, ".lisp") + defer done() + + f.GoToFile(t, "/app.lisp") + f.VerifyBaselineInlayHints(t, nil /*span*/, &lsutil.UserPreferences{InlayHints: lsutil.InlayHintsPreferences{IncludeInlayParameterNameHints: lsutil.IncludeInlayParameterNameHintsAll}}) +} diff --git a/tsc/internal/ls/inlay_hints.go b/tsc/internal/ls/inlay_hints.go index 0864f8df361e7..060ab7e5406e4 100644 --- a/tsc/internal/ls/inlay_hints.go +++ b/tsc/internal/ls/inlay_hints.go @@ -2,6 +2,7 @@ package ls import ( "context" + "reflect" "slices" "strings" "unicode" @@ -51,7 +52,17 @@ func (l *LanguageService) ProvideInlayHint( converters: l.converters, } inlayHintState.visit(projection.AsNode()) - result = append(result, inlayHintState.result...) + if len(result) == 0 { + result = append(result, inlayHintState.result...) + continue + } + for _, hint := range inlayHintState.result { + if !slices.ContainsFunc(result, func(existing *lsproto.InlayHint) bool { + return reflect.DeepEqual(existing, hint) + }) { + result = append(result, hint) + } + } } return lsproto.InlayHintsOrNull{InlayHints: &result}, nil } diff --git a/tsc/testdata/baselines/reference/fourslash/inlayHints/contentMapperInlayHints.baseline b/tsc/testdata/baselines/reference/fourslash/inlayHints/contentMapperInlayHints.baseline new file mode 100644 index 0000000000000..8ffd06588c5bb --- /dev/null +++ b/tsc/testdata/baselines/reference/fourslash/inlayHints/contentMapperInlayHints.baseline @@ -0,0 +1,96 @@ +// === Inlay Hints === +(+ 1 2 "oops") + ^ +{ + "position": { + "line": 0, + "character": 3 + }, + "label": [ + { + "value": "a", + "location": { + "uri": "file:///main.ts", + "range": { + "start": { + "line": 0, + "character": 21 + }, + "end": { + "line": 0, + "character": 22 + } + } + } + }, + { + "value": ":" + } + ], + "kind": 2, + "paddingRight": true +} + +(+ 1 2 "oops") + ^ +{ + "position": { + "line": 0, + "character": 5 + }, + "label": [ + { + "value": "b", + "location": { + "uri": "file:///main.ts", + "range": { + "start": { + "line": 0, + "character": 32 + }, + "end": { + "line": 0, + "character": 33 + } + } + } + }, + { + "value": ":" + } + ], + "kind": 2, + "paddingRight": true +} + +(+ 1 2 "oops") + ^ +{ + "position": { + "line": 0, + "character": 7 + }, + "label": [ + { + "value": "c", + "location": { + "uri": "file:///main.ts", + "range": { + "start": { + "line": 0, + "character": 43 + }, + "end": { + "line": 0, + "character": 44 + } + } + } + }, + { + "value": ":" + } + ], + "kind": 2, + "paddingRight": true +} \ No newline at end of file From 8a191c5e4c0d4786c5d9881d46280209de8a11d7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 24 Sep 2026 18:33:06 +0000 Subject: [PATCH 3/3] Avoid quadratic inlay hint deduplication Co-authored-by: andrewbranch <3277153+andrewbranch@users.noreply.github.com> --- tsc/internal/ls/inlay_hints.go | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/tsc/internal/ls/inlay_hints.go b/tsc/internal/ls/inlay_hints.go index 060ab7e5406e4..ab1b04646ac42 100644 --- a/tsc/internal/ls/inlay_hints.go +++ b/tsc/internal/ls/inlay_hints.go @@ -2,7 +2,6 @@ package ls import ( "context" - "reflect" "slices" "strings" "unicode" @@ -13,6 +12,7 @@ import ( "github.com/microsoft/TypeScript/tsc/internal/core" "github.com/microsoft/TypeScript/tsc/internal/debug" "github.com/microsoft/TypeScript/tsc/internal/evaluator" + "github.com/microsoft/TypeScript/tsc/internal/json" "github.com/microsoft/TypeScript/tsc/internal/ls/lsconv" "github.com/microsoft/TypeScript/tsc/internal/ls/lsutil" "github.com/microsoft/TypeScript/tsc/internal/lsp/lsproto" @@ -38,6 +38,10 @@ func (l *LanguageService) ProvideInlayHint( mappedRanges := l.converters.FromLSPRangeIntersectingForSourceFile(file, params.Range, spanmap.FeatureInlayHints) result := make([]*lsproto.InlayHint, 0, len(mappedRanges)) + var seen map[string]struct{} + if len(mappedRanges) > 1 { + seen = make(map[string]struct{}) + } for _, mapped := range mappedRanges { projection := mapped.Script checker, done := program.GetTypeCheckerForFile(ctx, projection) @@ -52,16 +56,21 @@ func (l *LanguageService) ProvideInlayHint( converters: l.converters, } inlayHintState.visit(projection.AsNode()) - if len(result) == 0 { + if seen == nil { result = append(result, inlayHintState.result...) continue } for _, hint := range inlayHintState.result { - if !slices.ContainsFunc(result, func(existing *lsproto.InlayHint) bool { - return reflect.DeepEqual(existing, hint) - }) { - result = append(result, hint) + encoded, err := json.Marshal(hint) + if err != nil { + return lsproto.InlayHintsOrNull{}, err + } + key := string(encoded) + if _, ok := seen[key]; ok { + continue } + seen[key] = struct{}{} + result = append(result, hint) } } return lsproto.InlayHintsOrNull{InlayHints: &result}, nil