From b5f81b2fc74695e95a4212271173941ab4aba289 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sun, 9 Mar 2025 15:00:10 +0100 Subject: [PATCH] Improve references search and quick info on properties with type errors within nullable contextual types --- src/services/services.ts | 1 + ...efsFromContextualUnionType1.baseline.jsonc | 121 ++++++++++++++++++ .../findAllRefsFromContextualUnionType1.ts | 10 ++ .../quickInfoFromContextualUnionType2.ts | 11 ++ .../quickInfoFromContextualUnionType3.ts | 33 +++++ 5 files changed, 176 insertions(+) create mode 100644 tests/baselines/reference/findAllRefsFromContextualUnionType1.baseline.jsonc create mode 100644 tests/cases/fourslash/findAllRefsFromContextualUnionType1.ts create mode 100644 tests/cases/fourslash/quickInfoFromContextualUnionType2.ts create mode 100644 tests/cases/fourslash/quickInfoFromContextualUnionType3.ts diff --git a/src/services/services.ts b/src/services/services.ts index 7236f146ff1c7..326ae3da6da0e 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -3558,6 +3558,7 @@ function getSymbolAtLocationForQuickInfo(node: Node, checker: TypeChecker): Symb * @internal */ export function getPropertySymbolsFromContextualType(node: ObjectLiteralElementWithName, checker: TypeChecker, contextualType: Type, unionSymbolOk: boolean): readonly Symbol[] { + contextualType = contextualType.getNonNullableType(); const name = getNameFromPropertyName(node.name); if (!name) return emptyArray; if (!contextualType.isUnion()) { diff --git a/tests/baselines/reference/findAllRefsFromContextualUnionType1.baseline.jsonc b/tests/baselines/reference/findAllRefsFromContextualUnionType1.baseline.jsonc new file mode 100644 index 0000000000000..7ac3843b6b5df --- /dev/null +++ b/tests/baselines/reference/findAllRefsFromContextualUnionType1.baseline.jsonc @@ -0,0 +1,121 @@ +// === findAllReferences === +// === /tests/cases/fourslash/findAllRefsFromContextualUnionType1.ts === +// function test1(arg: { <|[|prop|]: "foo"|> }) {} +// test1({ /*FIND ALL REFS*/<|[|{| isWriteAccess: true, isDefinition: true |}prop|]: "bar"|> }); +// +// function test2(arg: { prop: "foo" } | undefined) {} +// test2({ prop: "bar" }); + + // === Definitions === + // === /tests/cases/fourslash/findAllRefsFromContextualUnionType1.ts === + // function test1(arg: { <|[|prop|]: "foo"|> }) {} + // test1({ /*FIND ALL REFS*/prop: "bar" }); + // + // function test2(arg: { prop: "foo" } | undefined) {} + // test2({ prop: "bar" }); + + // === Details === + [ + { + "containerKind": "", + "containerName": "", + "kind": "property", + "name": "(property) prop: \"foo\"", + "displayParts": [ + { + "text": "(", + "kind": "punctuation" + }, + { + "text": "property", + "kind": "text" + }, + { + "text": ")", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "prop", + "kind": "propertyName" + }, + { + "text": ":", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "\"foo\"", + "kind": "stringLiteral" + } + ] + } + ] + + + +// === findAllReferences === +// === /tests/cases/fourslash/findAllRefsFromContextualUnionType1.ts === +// function test1(arg: { prop: "foo" }) {} +// test1({ prop: "bar" }); +// +// function test2(arg: { <|[|prop|]: "foo"|> } | undefined) {} +// test2({ /*FIND ALL REFS*/<|[|{| isWriteAccess: true, isDefinition: true |}prop|]: "bar"|> }); + + // === Definitions === + // === /tests/cases/fourslash/findAllRefsFromContextualUnionType1.ts === + // function test1(arg: { prop: "foo" }) {} + // test1({ prop: "bar" }); + // + // function test2(arg: { <|[|prop|]: "foo"|> } | undefined) {} + // test2({ /*FIND ALL REFS*/prop: "bar" }); + + // === Details === + [ + { + "containerKind": "", + "containerName": "", + "kind": "property", + "name": "(property) prop: \"foo\"", + "displayParts": [ + { + "text": "(", + "kind": "punctuation" + }, + { + "text": "property", + "kind": "text" + }, + { + "text": ")", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "prop", + "kind": "propertyName" + }, + { + "text": ":", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "\"foo\"", + "kind": "stringLiteral" + } + ] + } + ] \ No newline at end of file diff --git a/tests/cases/fourslash/findAllRefsFromContextualUnionType1.ts b/tests/cases/fourslash/findAllRefsFromContextualUnionType1.ts new file mode 100644 index 0000000000000..310d1f1d726bd --- /dev/null +++ b/tests/cases/fourslash/findAllRefsFromContextualUnionType1.ts @@ -0,0 +1,10 @@ +/// + +// @strict: true +//// function test1(arg: { prop: "foo" }) {} +//// test1({ /*1*/prop: "bar" }); +//// +//// function test2(arg: { prop: "foo" } | undefined) {} +//// test2({ /*2*/prop: "bar" }); + +verify.baselineFindAllReferences("1", "2"); \ No newline at end of file diff --git a/tests/cases/fourslash/quickInfoFromContextualUnionType2.ts b/tests/cases/fourslash/quickInfoFromContextualUnionType2.ts new file mode 100644 index 0000000000000..d67e5e839e222 --- /dev/null +++ b/tests/cases/fourslash/quickInfoFromContextualUnionType2.ts @@ -0,0 +1,11 @@ +/// + +// @strict: true +//// function test1(arg: { prop: "foo" }) {} +//// test1({ /*1*/prop: "bar" }); +//// +//// function test2(arg: { prop: "foo" } | undefined) {} +//// test2({ /*2*/prop: "bar" }); + +verify.quickInfoAt("1", '(property) prop: "foo"'); +verify.quickInfoAt("2", '(property) prop: "foo"'); \ No newline at end of file diff --git a/tests/cases/fourslash/quickInfoFromContextualUnionType3.ts b/tests/cases/fourslash/quickInfoFromContextualUnionType3.ts new file mode 100644 index 0000000000000..41a8a40512d5f --- /dev/null +++ b/tests/cases/fourslash/quickInfoFromContextualUnionType3.ts @@ -0,0 +1,33 @@ +/// + +// https://github.com/microsoft/TypeScript/issues/61382 + +// @strict: true +//// declare const foo1: >(definition: D) => D; +//// +//// type Foo1> = { +//// bar: { +//// [K in keyof Bar]: Bar[K] extends boolean +//// ? Bar[K] +//// : "Error: bar should be boolean"; +//// }; +//// }; +//// +//// declare const foo2: >(definition: D) => D; +//// +//// type Foo2> = { +//// bar?: { +//// [K in keyof Bar]: Bar[K] extends boolean +//// ? Bar[K] +//// : "Error: bar should be boolean"; +//// }; +//// }; +//// +//// type Prop = K extends keyof T ? T[K] : never; +//// +//// foo1({ bar: { /*1*/X: "test" } }); +//// +//// foo2({ bar: { /*2*/X: "test" } }); + +verify.quickInfoAt("1", '(property) X: "Error: bar should be boolean"'); +verify.quickInfoAt("2", '(property) X: "Error: bar should be boolean"'); \ No newline at end of file