From 06a97329f07fe41bb0031e657ea7e9fd0dab8e97 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Thu, 11 Dec 2025 01:39:21 +0000 Subject: [PATCH 1/4] Add test for when a JSX tag contains an unstructured type in a spread element. --- ...nJsxTagDifferentSpreadElementTypes_test.go | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 internal/fourslash/tests/completionsInJsxTagDifferentSpreadElementTypes_test.go diff --git a/internal/fourslash/tests/completionsInJsxTagDifferentSpreadElementTypes_test.go b/internal/fourslash/tests/completionsInJsxTagDifferentSpreadElementTypes_test.go new file mode 100644 index 0000000000..2324e6b1b3 --- /dev/null +++ b/internal/fourslash/tests/completionsInJsxTagDifferentSpreadElementTypes_test.go @@ -0,0 +1,63 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestCompletionsInJsxTagDifferentSpreadElementTypes(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = ` +// @Filename: /completionsWithDifferentSpreadTypes.tsx +// @strict: true + +// A reasonable type to spread. +export function ComponentObjectX(props: { x: string }) { + return ; +} + +// A questionable but valid type to spread. +export function ComponentObjectXOrY(props: { x: string } | { y: string }) { + return ; +} + +// A very unexpected type to spread (a union containing a primitive). +export function ComponentNumberOrObjectX(props: number | { x: string }) { + return ; +} + +// Very unexpected, but still structured (union) types. +export function ComponentBoolean(props: boolean) { + return ; +} +export function ComponentOptionalNull(props?: null) { + return ; +} + +// Primitive types (non-structured). +export function ComponentAny(props: any) { + return ; +} +export function ComponentUnknown(props: unknown) { + return ; +} +export function ComponentNever(props: never) { + return ; +} +export function ComponentUndefined(props: undefined) { + return ; +} +export function ComponentNumber(props: number) { + return ; +} +` + f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) + defer done() + f.GoToEachMarker(t, nil, func(marker *fourslash.Marker, index int) { + f.VerifyCompletions(t, marker, nil) + }) +} From b9cb0cd886cc3463b23aa8192b420d009653fcb4 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Thu, 11 Dec 2025 01:40:18 +0000 Subject: [PATCH 2/4] Add check for type flags before cast. --- internal/ls/completions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/ls/completions.go b/internal/ls/completions.go index 07cc863e55..bdf7deae13 100644 --- a/internal/ls/completions.go +++ b/internal/ls/completions.go @@ -4079,7 +4079,7 @@ func setMemberDeclaredBySpreadAssignment(declaration *ast.Node, members *collect t = typeChecker.GetTypeOfSymbolAtLocation(symbol, expression) } var properties []*ast.Symbol - if t != nil { + if t != nil && t.Flags()&checker.TypeFlagsStructuredType != 0 { properties = t.AsStructuredType().Properties() } for _, property := range properties { From 3b6ebfe361abba7fc50d17cfca6d3956cd16782a Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Wed, 10 Dec 2025 18:00:14 -0800 Subject: [PATCH 3/4] Apply suggestions from code review --- .../tests/completionsInJsxTagDifferentSpreadElementTypes_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/fourslash/tests/completionsInJsxTagDifferentSpreadElementTypes_test.go b/internal/fourslash/tests/completionsInJsxTagDifferentSpreadElementTypes_test.go index 2324e6b1b3..e7e4ef4573 100644 --- a/internal/fourslash/tests/completionsInJsxTagDifferentSpreadElementTypes_test.go +++ b/internal/fourslash/tests/completionsInJsxTagDifferentSpreadElementTypes_test.go @@ -31,6 +31,7 @@ export function ComponentNumberOrObjectX(props: number | { x: string }) { } // Very unexpected, but still structured (union) types. +// `boolean` is `true | false` and an optional `null` is really `null | undefined`. export function ComponentBoolean(props: boolean) { return ; } From d732cf517b0a1f427a23850962194cbd16f9a773 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Thu, 11 Dec 2025 02:36:30 +0000 Subject: [PATCH 4/4] Backticks!!!!!111```````` --- .../completionsInJsxTagDifferentSpreadElementTypes_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/fourslash/tests/completionsInJsxTagDifferentSpreadElementTypes_test.go b/internal/fourslash/tests/completionsInJsxTagDifferentSpreadElementTypes_test.go index e7e4ef4573..adf2889929 100644 --- a/internal/fourslash/tests/completionsInJsxTagDifferentSpreadElementTypes_test.go +++ b/internal/fourslash/tests/completionsInJsxTagDifferentSpreadElementTypes_test.go @@ -31,7 +31,7 @@ export function ComponentNumberOrObjectX(props: number | { x: string }) { } // Very unexpected, but still structured (union) types. -// `boolean` is `true | false` and an optional `null` is really `null | undefined`. +// 'boolean' is 'true | false' and an optional 'null' is really 'null | undefined'. export function ComponentBoolean(props: boolean) { return ; }