From 407edc95c09f5c4fbfa1d7bb3ff98f3dae1bee4c Mon Sep 17 00:00:00 2001 From: Oleksandr T Date: Sat, 4 Dec 2021 00:29:45 +0200 Subject: [PATCH] fix(46563): show completions at this type (#46581) --- src/compiler/checker.ts | 5 +---- src/compiler/types.ts | 1 + src/compiler/utilities.ts | 4 ++++ src/services/services.ts | 13 +++++++++++++ src/services/stringCompletions.ts | 2 +- src/services/types.ts | 2 ++ .../reference/api/tsserverlibrary.d.ts | 2 ++ tests/baselines/reference/api/typescript.d.ts | 2 ++ .../fourslash/completionListAtThisType.ts | 19 +++++++++++++++++++ 9 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 tests/cases/fourslash/completionListAtThisType.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 76e11d53811..b77b5a62177 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -432,6 +432,7 @@ namespace ts { getIndexInfosOfType, getSignaturesOfType, getIndexTypeOfType: (type, kind) => getIndexTypeOfType(type, kind === IndexKind.String ? stringType : numberType), + getIndexType: type => getIndexType(type), getBaseTypes, getBaseTypeOfLiteralType, getWidenedType, @@ -15344,10 +15345,6 @@ namespace ts { (type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) && !isPatternLiteralType(type) ? ObjectFlags.IsGenericIndexType : 0); } - function isThisTypeParameter(type: Type): boolean { - return !!(type.flags & TypeFlags.TypeParameter && (type as TypeParameter).isThisType); - } - function getSimplifiedType(type: Type, writing: boolean): Type { return type.flags & TypeFlags.IndexedAccess ? getSimplifiedIndexedAccessType(type as IndexedAccessType, writing) : type.flags & TypeFlags.Conditional ? getSimplifiedConditionalType(type as ConditionalType, writing) : diff --git a/src/compiler/types.ts b/src/compiler/types.ts index bdff510977b..e7e60dc0252 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4162,6 +4162,7 @@ namespace ts { getIndexInfosOfType(type: Type): readonly IndexInfo[]; getSignaturesOfType(type: Type, kind: SignatureKind): readonly Signature[]; getIndexTypeOfType(type: Type, kind: IndexKind): Type | undefined; + /* @internal */ getIndexType(type: Type): Type; getBaseTypes(type: InterfaceType): BaseType[]; getBaseTypeOfLiteralType(type: Type): Type; getWidenedType(type: Type): Type; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index a24f61a93fa..72ce071940d 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -7414,4 +7414,8 @@ namespace ts { export function escapeSnippetText(text: string): string { return text.replace(/\$/gm, "\\$"); } + + export function isThisTypeParameter(type: Type): boolean { + return !!(type.flags & TypeFlags.TypeParameter && (type as TypeParameter).isThisType); + } } diff --git a/src/services/services.ts b/src/services/services.ts index c9c54c0234e..c65f5221686 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -500,6 +500,9 @@ namespace ts { isClass(): this is InterfaceType { return !!(getObjectFlags(this) & ObjectFlags.Class); } + isIndexType(): this is IndexType { + return !!(this.flags & TypeFlags.Index); + } /** * This polyfills `referenceType.typeArguments` for API consumers */ @@ -545,6 +548,16 @@ namespace ts { getReturnType(): Type { return this.checker.getReturnTypeOfSignature(this); } + getTypeParameterAtPosition(pos: number): Type { + const type = this.checker.getParameterType(this, pos); + if (type.isIndexType() && isThisTypeParameter(type.type)) { + const constraint = type.type.getConstraint(); + if (constraint) { + return this.checker.getIndexType(constraint); + } + } + return type; + } getDocumentationComment(): SymbolDisplayPart[] { return this.documentationComment || (this.documentationComment = getDocumentationComment(singleElementArray(this.declaration), this.checker)); diff --git a/src/services/stringCompletions.ts b/src/services/stringCompletions.ts index 2c1a453df6d..b84b578ed4f 100644 --- a/src/services/stringCompletions.ts +++ b/src/services/stringCompletions.ts @@ -264,7 +264,7 @@ namespace ts.Completions.StringCompletions { checker.getResolvedSignature(argumentInfo.invocation, candidates, argumentInfo.argumentCount); const types = flatMap(candidates, candidate => { if (!signatureHasRestParameter(candidate) && argumentInfo.argumentCount > candidate.parameters.length) return; - const type = checker.getParameterType(candidate, argumentInfo.argumentIndex); + const type = candidate.getTypeParameterAtPosition(argumentInfo.argumentIndex); isNewIdentifier = isNewIdentifier || !!(type.flags & TypeFlags.String); return getStringLiteralTypes(type, uniques); }); diff --git a/src/services/types.ts b/src/services/types.ts index 0659e6295e6..b9bd9f2177d 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -72,6 +72,7 @@ namespace ts { isTypeParameter(): this is TypeParameter; isClassOrInterface(): this is InterfaceType; isClass(): this is InterfaceType; + isIndexType(): this is IndexType; } export interface TypeReference { @@ -82,6 +83,7 @@ namespace ts { getDeclaration(): SignatureDeclaration; getTypeParameters(): TypeParameter[] | undefined; getParameters(): Symbol[]; + getTypeParameterAtPosition(pos: number): Type; getReturnType(): Type; getDocumentationComment(typeChecker: TypeChecker | undefined): SymbolDisplayPart[]; getJsDocTags(): JSDocTagInfo[]; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 752f6b892bb..c332bd508eb 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -5560,6 +5560,7 @@ declare namespace ts { isTypeParameter(): this is TypeParameter; isClassOrInterface(): this is InterfaceType; isClass(): this is InterfaceType; + isIndexType(): this is IndexType; } interface TypeReference { typeArguments?: readonly Type[]; @@ -5568,6 +5569,7 @@ declare namespace ts { getDeclaration(): SignatureDeclaration; getTypeParameters(): TypeParameter[] | undefined; getParameters(): Symbol[]; + getTypeParameterAtPosition(pos: number): Type; getReturnType(): Type; getDocumentationComment(typeChecker: TypeChecker | undefined): SymbolDisplayPart[]; getJsDocTags(): JSDocTagInfo[]; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index a9d7e9c449b..f17b673f250 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -5560,6 +5560,7 @@ declare namespace ts { isTypeParameter(): this is TypeParameter; isClassOrInterface(): this is InterfaceType; isClass(): this is InterfaceType; + isIndexType(): this is IndexType; } interface TypeReference { typeArguments?: readonly Type[]; @@ -5568,6 +5569,7 @@ declare namespace ts { getDeclaration(): SignatureDeclaration; getTypeParameters(): TypeParameter[] | undefined; getParameters(): Symbol[]; + getTypeParameterAtPosition(pos: number): Type; getReturnType(): Type; getDocumentationComment(typeChecker: TypeChecker | undefined): SymbolDisplayPart[]; getJsDocTags(): JSDocTagInfo[]; diff --git a/tests/cases/fourslash/completionListAtThisType.ts b/tests/cases/fourslash/completionListAtThisType.ts new file mode 100644 index 00000000000..3842b6c3cfe --- /dev/null +++ b/tests/cases/fourslash/completionListAtThisType.ts @@ -0,0 +1,19 @@ +/// + +////class Test { +//// foo() {} +//// +//// bar() { +//// this.baz(this, "/*1*/"); +//// +//// const t = new Test() +//// this.baz(t, "/*2*/"); +//// } +//// +//// baz(a: T, k: keyof T) {} +////} + +verify.completions({ + marker: ["1", "2"], + exact: ["foo", "bar", "baz"] +});