Skip to content

Commit b2327c0

Browse files
authored
Make go to definition go to the constraint's properties for object literals in argument positions (#62361)
1 parent dafed7f commit b2327c0

File tree

9 files changed

+312
-13
lines changed

9 files changed

+312
-13
lines changed

src/compiler/checker.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1748,7 +1748,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
17481748
if (!node) {
17491749
return undefined;
17501750
}
1751-
if (contextFlags! & ContextFlags.Completions) {
1751+
if (contextFlags! & ContextFlags.IgnoreNodeInferences) {
17521752
return runWithInferenceBlockedFromSourceNode(node, () => getContextualType(node, contextFlags));
17531753
}
17541754
return getContextualType(node, contextFlags);
@@ -32845,7 +32845,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3284532845
}
3284632846

3284732847
function getContextualJsxElementAttributesType(node: JsxOpeningLikeElement, contextFlags: ContextFlags | undefined) {
32848-
if (isJsxOpeningElement(node) && contextFlags !== ContextFlags.Completions) {
32848+
if (isJsxOpeningElement(node) && contextFlags !== ContextFlags.IgnoreNodeInferences) {
3284932849
const index = findContextualNode(node.parent, /*includeCaches*/ !contextFlags);
3285032850
if (index >= 0) {
3285132851
// Contextually applied type is moved from attributes up to the outer jsx attributes so when walking up from the children they get hit

src/compiler/types.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5515,11 +5515,11 @@ export const enum IntersectionFlags {
55155515
// dprint-ignore
55165516
/** @internal */
55175517
export const enum ContextFlags {
5518-
None = 0,
5519-
Signature = 1 << 0, // Obtaining contextual signature
5520-
NoConstraints = 1 << 1, // Don't obtain type variable constraints
5521-
Completions = 1 << 2, // Ignore inference to current node and parent nodes out to the containing call for completions
5522-
SkipBindingPatterns = 1 << 3, // Ignore contextual types applied by binding patterns
5518+
None = 0,
5519+
Signature = 1 << 0, // Obtaining contextual signature
5520+
NoConstraints = 1 << 1, // Don't obtain type variable constraints
5521+
IgnoreNodeInferences = 1 << 2, // Ignore inference to current node and parent nodes out to the containing call for, for example, completions
5522+
SkipBindingPatterns = 1 << 3, // Ignore contextual types applied by binding patterns
55235523
}
55245524

55255525
// NOTE: If modifying this enum, must modify `TypeFormatFlags` too!

src/services/completions.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3280,7 +3280,7 @@ function getContextualType(previousToken: Node, position: number, sourceFile: So
32803280
isEqualityOperatorKind(previousToken.kind) && isBinaryExpression(parent) && isEqualityOperatorKind(parent.operatorToken.kind) ?
32813281
// completion at `x ===/**/` should be for the right side
32823282
checker.getTypeAtLocation(parent.left) :
3283-
checker.getContextualType(previousToken as Expression, ContextFlags.Completions) || checker.getContextualType(previousToken as Expression);
3283+
checker.getContextualType(previousToken as Expression, ContextFlags.IgnoreNodeInferences) || checker.getContextualType(previousToken as Expression);
32843284
}
32853285
}
32863286

@@ -3966,7 +3966,7 @@ function getCompletionData(
39663966
// Cursor is inside a JSX self-closing element or opening element
39673967
const attrsType = jsxContainer && typeChecker.getContextualType(jsxContainer.attributes);
39683968
if (!attrsType) return GlobalsSearch.Continue;
3969-
const completionsType = jsxContainer && typeChecker.getContextualType(jsxContainer.attributes, ContextFlags.Completions);
3969+
const completionsType = jsxContainer && typeChecker.getContextualType(jsxContainer.attributes, ContextFlags.IgnoreNodeInferences);
39703970
symbols = concatenate(symbols, filterJsxAttributes(getPropertiesForObjectExpression(attrsType, completionsType, jsxContainer.attributes, typeChecker), jsxContainer.attributes.properties));
39713971
setSortTextToOptionalMember();
39723972
completionKind = CompletionKind.MemberLike;
@@ -4564,7 +4564,7 @@ function getCompletionData(
45644564
}
45654565
return GlobalsSearch.Continue;
45664566
}
4567-
const completionsType = typeChecker.getContextualType(objectLikeContainer, ContextFlags.Completions);
4567+
const completionsType = typeChecker.getContextualType(objectLikeContainer, ContextFlags.IgnoreNodeInferences);
45684568
const hasStringIndexType = (completionsType || instantiatedType).getStringIndexType();
45694569
const hasNumberIndextype = (completionsType || instantiatedType).getNumberIndexType();
45704570
isNewIdentifierLocation = !!hasStringIndexType || !!hasNumberIndextype;

src/services/goToDefinition.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
CallLikeExpression,
77
canHaveSymbol,
88
concatenate,
9+
ContextFlags,
910
createTextSpan,
1011
createTextSpanFromBounds,
1112
createTextSpanFromNode,
@@ -72,6 +73,8 @@ import {
7273
isNameOfFunctionDeclaration,
7374
isNewExpressionTarget,
7475
isObjectBindingPattern,
76+
isObjectLiteralElementLike,
77+
isObjectLiteralExpression,
7578
isPropertyName,
7679
isRightSideOfPropertyAccess,
7780
isStaticModifier,
@@ -312,7 +315,17 @@ function getDefinitionFromObjectLiteralElement(typeChecker: TypeChecker, node: N
312315
if (element) {
313316
const contextualType = element && typeChecker.getContextualType(element.parent);
314317
if (contextualType) {
315-
return flatMap(getPropertySymbolsFromContextualType(element, typeChecker, contextualType, /*unionSymbolOk*/ false), propertySymbol => getDefinitionFromSymbol(typeChecker, propertySymbol, node));
318+
let properties = getPropertySymbolsFromContextualType(element, typeChecker, contextualType, /*unionSymbolOk*/ false);
319+
if (some(properties, p => !!(p.valueDeclaration && isObjectLiteralExpression(p.valueDeclaration.parent) && isObjectLiteralElementLike(p.valueDeclaration) && p.valueDeclaration.name === node))) {
320+
const withoutNodeInferencesType = typeChecker.getContextualType(element.parent, ContextFlags.IgnoreNodeInferences);
321+
if (withoutNodeInferencesType) {
322+
const withoutNodeInferencesProperties = getPropertySymbolsFromContextualType(element, typeChecker, withoutNodeInferencesType, /*unionSymbolOk*/ false);
323+
if (withoutNodeInferencesProperties.length) {
324+
properties = withoutNodeInferencesProperties;
325+
}
326+
}
327+
}
328+
return flatMap(properties, propertySymbol => getDefinitionFromSymbol(typeChecker, propertySymbol, node));
316329
}
317330
}
318331
return emptyArray;

src/services/stringCompletions.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -553,7 +553,7 @@ function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringL
553553
}
554554
}
555555

556-
function fromContextualType(contextFlags: ContextFlags = ContextFlags.Completions): StringLiteralCompletionsFromTypes | undefined {
556+
function fromContextualType(contextFlags: ContextFlags = ContextFlags.IgnoreNodeInferences): StringLiteralCompletionsFromTypes | undefined {
557557
// Get completion for string literal from string literal type
558558
// i.e. var x: "hi" | "hello" = "/*completion position*/"
559559
const types = getStringLiteralTypes(getContextualTypeFromParent(node, typeChecker, contextFlags));
@@ -611,7 +611,7 @@ function stringLiteralCompletionsForObjectLiteral(checker: TypeChecker, objectLi
611611
const contextualType = checker.getContextualType(objectLiteralExpression);
612612
if (!contextualType) return undefined;
613613

614-
const completionsType = checker.getContextualType(objectLiteralExpression, ContextFlags.Completions);
614+
const completionsType = checker.getContextualType(objectLiteralExpression, ContextFlags.IgnoreNodeInferences);
615615
const symbols = getPropertiesForObjectExpression(
616616
contextualType,
617617
completionsType,
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// === goToDefinition ===
2+
// === /tests/cases/fourslash/goToDefinitionObjectLiteralProperties2.ts ===
3+
// type C = {
4+
// <|[|foo|]: string;|>
5+
// bar: number;
6+
// };
7+
//
8+
// declare function fn<T extends C>(arg: T): T;
9+
//
10+
// fn({
11+
// foo/*GOTO DEF*/: "",
12+
// bar: true,
13+
// });
14+
//
15+
// --- (line: 13) skipped ---
16+
17+
// === Details ===
18+
[
19+
{
20+
"kind": "property",
21+
"name": "foo",
22+
"containerName": "__type",
23+
"isLocal": false,
24+
"isAmbient": false,
25+
"unverified": false
26+
}
27+
]
28+
29+
30+
31+
// === goToDefinition ===
32+
// === /tests/cases/fourslash/goToDefinitionObjectLiteralProperties2.ts ===
33+
// type C = {
34+
// foo: string;
35+
// <|[|bar|]: number;|>
36+
// };
37+
//
38+
// declare function fn<T extends C>(arg: T): T;
39+
//
40+
// fn({
41+
// foo: "",
42+
// bar/*GOTO DEF*/: true,
43+
// });
44+
//
45+
// const result = fn({
46+
// --- (line: 14) skipped ---
47+
48+
// === Details ===
49+
[
50+
{
51+
"kind": "property",
52+
"name": "bar",
53+
"containerName": "__type",
54+
"isLocal": false,
55+
"isAmbient": false,
56+
"unverified": false
57+
}
58+
]
59+
60+
61+
62+
// === goToDefinition ===
63+
// === /tests/cases/fourslash/goToDefinitionObjectLiteralProperties2.ts ===
64+
// type C = {
65+
// <|[|foo|]: string;|>
66+
// bar: number;
67+
// };
68+
//
69+
// --- (line: 6) skipped ---
70+
71+
// --- (line: 10) skipped ---
72+
// });
73+
//
74+
// const result = fn({
75+
// foo/*GOTO DEF*/: "",
76+
// bar: 1,
77+
// });
78+
//
79+
// // this one shouldn't go to the constraint type
80+
// result.foo;
81+
82+
// === Details ===
83+
[
84+
{
85+
"kind": "property",
86+
"name": "foo",
87+
"containerName": "__type",
88+
"isLocal": false,
89+
"isAmbient": false,
90+
"unverified": false
91+
}
92+
]
93+
94+
95+
96+
// === goToDefinition ===
97+
// === /tests/cases/fourslash/goToDefinitionObjectLiteralProperties2.ts ===
98+
// type C = {
99+
// foo: string;
100+
// <|[|bar|]: number;|>
101+
// };
102+
//
103+
// declare function fn<T extends C>(arg: T): T;
104+
// --- (line: 7) skipped ---
105+
106+
// --- (line: 11) skipped ---
107+
//
108+
// const result = fn({
109+
// foo: "",
110+
// bar/*GOTO DEF*/: 1,
111+
// });
112+
//
113+
// // this one shouldn't go to the constraint type
114+
// result.foo;
115+
116+
// === Details ===
117+
[
118+
{
119+
"kind": "property",
120+
"name": "bar",
121+
"containerName": "__type",
122+
"isLocal": false,
123+
"isAmbient": false,
124+
"unverified": false
125+
}
126+
]
127+
128+
129+
130+
// === goToDefinition ===
131+
// === /tests/cases/fourslash/goToDefinitionObjectLiteralProperties2.ts ===
132+
// --- (line: 10) skipped ---
133+
// });
134+
//
135+
// const result = fn({
136+
// <|[|foo|]: ""|>,
137+
// bar: 1,
138+
// });
139+
//
140+
// // this one shouldn't go to the constraint type
141+
// result.foo/*GOTO DEF*/;
142+
143+
// === Details ===
144+
[
145+
{
146+
"kind": "property",
147+
"name": "foo",
148+
"containerName": "__object",
149+
"isLocal": false,
150+
"isAmbient": false,
151+
"unverified": false,
152+
"failedAliasResolution": false
153+
}
154+
]
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// === goToDefinition ===
2+
// === /tests/cases/fourslash/goToDefinitionObjectLiteralProperties3.ts ===
3+
// type A = {
4+
// <|[|{| defId: 0 |}foo|]: unknown;|>
5+
// };
6+
//
7+
// type B = {
8+
// <|[|{| defId: 1 |}foo|]?: unknown;|>
9+
// bar: unknown;
10+
// };
11+
//
12+
// function test1(arg: A | B) {}
13+
//
14+
// test1({
15+
// foo/*GOTO DEF*/: 1,
16+
// });
17+
//
18+
// function test2<T extends A>(arg: T | B) {}
19+
// --- (line: 17) skipped ---
20+
21+
// === Details ===
22+
[
23+
{
24+
"defId": 0,
25+
"kind": "property",
26+
"name": "foo",
27+
"containerName": "__type",
28+
"isLocal": false,
29+
"isAmbient": false,
30+
"unverified": false
31+
},
32+
{
33+
"defId": 1,
34+
"kind": "property",
35+
"name": "foo",
36+
"containerName": "__type",
37+
"isLocal": false,
38+
"isAmbient": false,
39+
"unverified": false
40+
}
41+
]
42+
43+
44+
45+
// === goToDefinition ===
46+
// === /tests/cases/fourslash/goToDefinitionObjectLiteralProperties3.ts ===
47+
// type A = {
48+
// <|[|{| defId: 0 |}foo|]: unknown;|>
49+
// };
50+
//
51+
// type B = {
52+
// <|[|{| defId: 1 |}foo|]?: unknown;|>
53+
// bar: unknown;
54+
// };
55+
//
56+
// --- (line: 10) skipped ---
57+
58+
// --- (line: 15) skipped ---
59+
// function test2<T extends A>(arg: T | B) {}
60+
//
61+
// test2({
62+
// foo/*GOTO DEF*/: 2,
63+
// });
64+
65+
// === Details ===
66+
[
67+
{
68+
"defId": 0,
69+
"kind": "property",
70+
"name": "foo",
71+
"containerName": "__type",
72+
"isLocal": false,
73+
"isAmbient": false,
74+
"unverified": false
75+
},
76+
{
77+
"defId": 1,
78+
"kind": "property",
79+
"name": "foo",
80+
"containerName": "__type",
81+
"isLocal": false,
82+
"isAmbient": false,
83+
"unverified": false
84+
}
85+
]
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
//// type C = {
4+
//// foo: string;
5+
//// bar: number;
6+
//// };
7+
////
8+
//// declare function fn<T extends C>(arg: T): T;
9+
////
10+
//// fn({
11+
//// foo/*1*/: "",
12+
//// bar/*2*/: true,
13+
//// });
14+
////
15+
//// const result = fn({
16+
//// foo/*3*/: "",
17+
//// bar/*4*/: 1,
18+
//// });
19+
////
20+
//// // this one shouldn't go to the constraint type
21+
//// result.foo/*5*/;
22+
23+
verify.baselineGoToDefinition("1", "2", "3", "4", "5");

0 commit comments

Comments
 (0)