Skip to content

Commit 83ce8a4

Browse files
committed
Walk index type structure a bit to find type parameter in it
1 parent 2f798a5 commit 83ce8a4

File tree

3 files changed

+120
-3
lines changed

3 files changed

+120
-3
lines changed

src/services/completions.ts

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1279,7 +1279,14 @@ namespace ts.Completions {
12791279
// Cursor is inside a JSX self-closing element or opening element
12801280
const attrsType = jsxContainer && typeChecker.getContextualType(jsxContainer.attributes);
12811281
if (!attrsType) return GlobalsSearch.Continue;
1282-
const baseType = jsxContainer && typeChecker.getContextualType(jsxContainer.attributes, ContextFlags.BaseConstraint);
1282+
const uninstantiatedType = typeChecker.getContextualType(jsxContainer!, ContextFlags.Uninstantiated);
1283+
let baseType;
1284+
if (uninstantiatedType) {
1285+
const signature = tryGetContextualTypeProvidingSignature(jsxContainer!, typeChecker)?.target;
1286+
if (signature && !isIndexedAccessTypeWithTypeParameterIndex(uninstantiatedType, signature)) {
1287+
baseType = typeChecker.getContextualType(jsxContainer!.attributes, ContextFlags.BaseConstraint);
1288+
}
1289+
}
12831290
symbols = filterJsxAttributes(getPropertiesForObjectExpression(attrsType, baseType, jsxContainer!.attributes, typeChecker), jsxContainer!.attributes.properties);
12841291
setSortTextToOptionalMember();
12851292
completionKind = CompletionKind.MemberLike;
@@ -1803,8 +1810,11 @@ namespace ts.Completions {
18031810
if (!instantiatedType) return GlobalsSearch.Fail;
18041811
const uninstantiatedType = typeChecker.getContextualType(objectLikeContainer, ContextFlags.Uninstantiated);
18051812
let baseType;
1806-
if (!(uninstantiatedType && uninstantiatedType.flags & TypeFlags.IndexedAccess)) {
1807-
baseType = typeChecker.getContextualType(objectLikeContainer, ContextFlags.BaseConstraint);
1813+
if (uninstantiatedType) {
1814+
const signature = tryGetContextualTypeProvidingSignature(objectLikeContainer, typeChecker)?.target;
1815+
if (signature && !isIndexedAccessTypeWithTypeParameterIndex(uninstantiatedType, signature)) {
1816+
baseType = typeChecker.getContextualType(objectLikeContainer, ContextFlags.BaseConstraint);
1817+
}
18081818
}
18091819

18101820
isNewIdentifierLocation = hasIndexSignature(instantiatedType);
@@ -1857,6 +1867,62 @@ namespace ts.Completions {
18571867
return GlobalsSearch.Success;
18581868
}
18591869

1870+
function tryGetContextualTypeProvidingSignature(node: Node, checker: TypeChecker): Signature | undefined {
1871+
loop: while (true) {
1872+
switch (node.kind) {
1873+
case SyntaxKind.SpreadAssignment:
1874+
case SyntaxKind.ArrayLiteralExpression:
1875+
case SyntaxKind.ParenthesizedExpression:
1876+
case SyntaxKind.ConditionalExpression:
1877+
case SyntaxKind.PropertyAssignment:
1878+
case SyntaxKind.ShorthandPropertyAssignment:
1879+
case SyntaxKind.ObjectLiteralExpression:
1880+
case SyntaxKind.JsxAttribute:
1881+
case SyntaxKind.JsxAttributes:
1882+
node = node.parent;
1883+
break;
1884+
default:
1885+
break loop;
1886+
}
1887+
}
1888+
if (!isCallLikeExpression(node) && !isJsxOpeningLikeElement(node)) {
1889+
return;
1890+
}
1891+
return checker.getResolvedSignature(node);
1892+
}
1893+
1894+
function isIndexedAccessTypeWithTypeParameterIndex(type: Type, signature: Signature): boolean {
1895+
if (type.isUnionOrIntersection()) {
1896+
return some(type.types, t => isIndexedAccessTypeWithTypeParameterIndex(t, signature));
1897+
}
1898+
if (type.flags & TypeFlags.IndexedAccess) {
1899+
return typeIsTypeParameterFromSignature((type as IndexedAccessType).indexType, signature);
1900+
}
1901+
return false;
1902+
}
1903+
1904+
function typeIsTypeParameterFromSignature(type: Type, signature: Signature): boolean {
1905+
if (!signature.typeParameters) {
1906+
return false;
1907+
}
1908+
if (type.isUnionOrIntersection()) {
1909+
return some(type.types, t => typeIsTypeParameterFromSignature(t, signature));
1910+
}
1911+
if (type.flags & TypeFlags.Conditional) {
1912+
return typeIsTypeParameterFromSignature((type as ConditionalType).checkType, signature)
1913+
|| typeIsTypeParameterFromSignature((type as ConditionalType).extendsType, signature)
1914+
|| typeIsTypeParameterFromSignature((type as ConditionalType).resolvedTrueType, signature)
1915+
|| typeIsTypeParameterFromSignature((type as ConditionalType).resolvedFalseType, signature);
1916+
}
1917+
if (type.flags & TypeFlags.Index) {
1918+
return typeIsTypeParameterFromSignature((type as IndexType).type, signature);
1919+
}
1920+
if (type.flags & TypeFlags.TypeParameter) {
1921+
return some(signature.typeParameters, p => p.symbol === type.symbol);
1922+
}
1923+
return false;
1924+
}
1925+
18601926
/**
18611927
* Aggregates relevant symbols for completion in import clauses and export clauses
18621928
* whose declarations have a module specifier; for instance, symbols will be aggregated for
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
////interface CustomElements {
2+
//// 'component-one': {
3+
//// foo?: string;
4+
//// },
5+
//// 'component-two': {
6+
//// bar?: string;
7+
//// }
8+
////}
9+
////
10+
////interface Options<T extends keyof CustomElements> {
11+
//// props?: {} & { x: CustomElements[(T extends string ? T : never) & string][] }['x'];
12+
////}
13+
////
14+
////declare function f<T extends keyof CustomElements>(k: T, options: Options<T>): void;
15+
////
16+
////f("component-one", {
17+
//// props: [{
18+
//// /**/
19+
//// }]
20+
////})
21+
22+
verify.completions({
23+
marker: "",
24+
exact: [{
25+
name: "foo",
26+
sortText: completion.SortText.OptionalMember
27+
}]
28+
});
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// @Filename: component.tsx
2+
3+
////interface CustomElements {
4+
//// 'component-one': {
5+
//// foo?: string;
6+
//// },
7+
//// 'component-two': {
8+
//// bar?: string;
9+
//// }
10+
////}
11+
////
12+
////type Options<T extends keyof CustomElements> = { kind: T } & Required<{ x: CustomElements[(T extends string ? T : never) & string] }['x']>;
13+
////
14+
////declare function Component<T extends keyof CustomElements>(props: Options<T>): void;
15+
////
16+
////const c = <Component /**/ kind="component-one" />
17+
18+
verify.completions({
19+
marker: "",
20+
exact: [{
21+
name: "foo"
22+
}]
23+
})

0 commit comments

Comments
 (0)