Skip to content

Commit 9942c60

Browse files
Kingwlandrewbranch
authored andcommitted
add completion for promise context (#32101)
* add completion for promise context * check insert text inside add symbol helper * fix incorrect branch * avoid completions with includeCompletionsWithInsertText perferences * avoid useless parameter
1 parent e9073a8 commit 9942c60

7 files changed

+140
-8
lines changed

src/services/completions.ts

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ namespace ts.Completions {
99
}
1010
export type Log = (message: string) => void;
1111

12-
const enum SymbolOriginInfoKind { ThisType, SymbolMemberNoExport, SymbolMemberExport, Export }
13-
type SymbolOriginInfo = { kind: SymbolOriginInfoKind.ThisType } | { kind: SymbolOriginInfoKind.SymbolMemberNoExport } | SymbolOriginInfoExport;
12+
const enum SymbolOriginInfoKind { ThisType, SymbolMemberNoExport, SymbolMemberExport, Export, Promise }
13+
type SymbolOriginInfo = { kind: SymbolOriginInfoKind.ThisType } | { kind: SymbolOriginInfoKind.Promise } | { kind: SymbolOriginInfoKind.SymbolMemberNoExport } | SymbolOriginInfoExport;
1414
interface SymbolOriginInfoExport {
1515
kind: SymbolOriginInfoKind.SymbolMemberExport | SymbolOriginInfoKind.Export;
1616
moduleSymbol: Symbol;
@@ -22,6 +22,9 @@ namespace ts.Completions {
2222
function originIsExport(origin: SymbolOriginInfo): origin is SymbolOriginInfoExport {
2323
return origin.kind === SymbolOriginInfoKind.SymbolMemberExport || origin.kind === SymbolOriginInfoKind.Export;
2424
}
25+
function originIsPromise(origin: SymbolOriginInfo): boolean {
26+
return origin.kind === SymbolOriginInfoKind.Promise;
27+
}
2528

2629
/**
2730
* Map from symbol id -> SymbolOriginInfo.
@@ -264,6 +267,12 @@ namespace ts.Completions {
264267
replacementSpan = createTextSpanFromNode(isJsxInitializer, sourceFile);
265268
}
266269
}
270+
if (origin && originIsPromise(origin) && propertyAccessToConvert) {
271+
if (insertText === undefined) insertText = name;
272+
const awaitText = `(await ${propertyAccessToConvert.expression.getText()})`;
273+
insertText = needsConvertPropertyAccess ? `${awaitText}${insertText}` : `${awaitText}.${insertText}`;
274+
replacementSpan = createTextSpanFromBounds(propertyAccessToConvert.getStart(sourceFile), propertyAccessToConvert.end);
275+
}
267276

268277
if (insertText !== undefined && !preferences.includeCompletionsWithInsertText) {
269278
return undefined;
@@ -313,7 +322,7 @@ namespace ts.Completions {
313322
log: Log,
314323
kind: CompletionKind,
315324
preferences: UserPreferences,
316-
propertyAccessToConvert?: PropertyAccessExpression | undefined,
325+
propertyAccessToConvert?: PropertyAccessExpression,
317326
isJsxInitializer?: IsJsxInitializer,
318327
recommendedCompletion?: Symbol,
319328
symbolToOriginInfoMap?: SymbolOriginInfoMap,
@@ -984,7 +993,7 @@ namespace ts.Completions {
984993
if (!isTypeLocation &&
985994
symbol.declarations &&
986995
symbol.declarations.some(d => d.kind !== SyntaxKind.SourceFile && d.kind !== SyntaxKind.ModuleDeclaration && d.kind !== SyntaxKind.EnumDeclaration)) {
987-
addTypeProperties(typeChecker.getTypeOfSymbolAtLocation(symbol, node));
996+
addTypeProperties(typeChecker.getTypeOfSymbolAtLocation(symbol, node), !!(node.flags & NodeFlags.AwaitContext));
988997
}
989998

990999
return;
@@ -999,13 +1008,14 @@ namespace ts.Completions {
9991008
}
10001009

10011010
if (!isTypeLocation) {
1002-
addTypeProperties(typeChecker.getTypeAtLocation(node));
1011+
addTypeProperties(typeChecker.getTypeAtLocation(node), !!(node.flags & NodeFlags.AwaitContext));
10031012
}
10041013
}
10051014

1006-
function addTypeProperties(type: Type): void {
1015+
function addTypeProperties(type: Type, insertAwait?: boolean): void {
10071016
isNewIdentifierLocation = !!type.getStringIndexType();
10081017

1018+
const propertyAccess = node.kind === SyntaxKind.ImportType ? <ImportTypeNode>node : <PropertyAccessExpression | QualifiedName>node.parent;
10091019
if (isUncheckedFile) {
10101020
// In javascript files, for union types, we don't just get the members that
10111021
// the individual types have in common, we also include all the members that
@@ -1016,14 +1026,25 @@ namespace ts.Completions {
10161026
}
10171027
else {
10181028
for (const symbol of type.getApparentProperties()) {
1019-
if (typeChecker.isValidPropertyAccessForCompletions(node.kind === SyntaxKind.ImportType ? <ImportTypeNode>node : <PropertyAccessExpression | QualifiedName>node.parent, type, symbol)) {
1029+
if (typeChecker.isValidPropertyAccessForCompletions(propertyAccess, type, symbol)) {
10201030
addPropertySymbol(symbol);
10211031
}
10221032
}
10231033
}
1034+
1035+
if (insertAwait && preferences.includeCompletionsWithInsertText) {
1036+
const promiseType = typeChecker.getPromisedTypeOfPromise(type);
1037+
if (promiseType) {
1038+
for (const symbol of promiseType.getApparentProperties()) {
1039+
if (typeChecker.isValidPropertyAccessForCompletions(propertyAccess, promiseType, symbol)) {
1040+
addPropertySymbol(symbol, /* insertAwait */ true);
1041+
}
1042+
}
1043+
}
1044+
}
10241045
}
10251046

1026-
function addPropertySymbol(symbol: Symbol) {
1047+
function addPropertySymbol(symbol: Symbol, insertAwait?: boolean) {
10271048
// For a computed property with an accessible name like `Symbol.iterator`,
10281049
// we'll add a completion for the *name* `Symbol` instead of for the property.
10291050
// If this is e.g. [Symbol.iterator], add a completion for `Symbol`.
@@ -1040,12 +1061,20 @@ namespace ts.Completions {
10401061
!moduleSymbol || !isExternalModuleSymbol(moduleSymbol) ? { kind: SymbolOriginInfoKind.SymbolMemberNoExport } : { kind: SymbolOriginInfoKind.SymbolMemberExport, moduleSymbol, isDefaultExport: false };
10411062
}
10421063
else if (preferences.includeCompletionsWithInsertText) {
1064+
addPromiseSymbolOriginInfo(symbol);
10431065
symbols.push(symbol);
10441066
}
10451067
}
10461068
else {
1069+
addPromiseSymbolOriginInfo(symbol);
10471070
symbols.push(symbol);
10481071
}
1072+
1073+
function addPromiseSymbolOriginInfo (symbol: Symbol) {
1074+
if (insertAwait && preferences.includeCompletionsWithInsertText && !symbolToOriginInfoMap[getSymbolId(symbol)]) {
1075+
symbolToOriginInfoMap[getSymbolId(symbol)] = { kind: SymbolOriginInfoKind.Promise };
1076+
}
1077+
}
10491078
}
10501079

10511080
/** Given 'a.b.c', returns 'a'. */
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
//// async function foo(x: Promise<string>) {
4+
//// [|x./**/|]
5+
//// }
6+
7+
const replacementSpan = test.ranges()[0]
8+
verify.completions({
9+
marker: "",
10+
includes: [
11+
"then",
12+
{ name: "trim", insertText: '(await x).trim', replacementSpan },
13+
],
14+
preferences: {
15+
includeInsertTextCompletions: true,
16+
},
17+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
//// interface Foo { foo: string }
4+
//// async function foo(x: Promise<Foo>) {
5+
//// [|x./**/|]
6+
//// }
7+
8+
const replacementSpan = test.ranges()[0]
9+
verify.completions({
10+
marker: "",
11+
includes: [
12+
"then",
13+
{ name: "foo", insertText: '(await x).foo', replacementSpan },
14+
],
15+
preferences: {
16+
includeInsertTextCompletions: true,
17+
},
18+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
//// interface Foo { ["foo-foo"]: string }
4+
//// async function foo(x: Promise<Foo>) {
5+
//// [|x./**/|]
6+
//// }
7+
8+
const replacementSpan = test.ranges()[0]
9+
verify.completions({
10+
marker: "",
11+
includes: [
12+
"then",
13+
{ name: "foo-foo", insertText: '(await x)["foo-foo"]', replacementSpan, },
14+
],
15+
preferences: {
16+
includeInsertTextCompletions: true,
17+
},
18+
});
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
//// function foo(x: Promise<string>) {
4+
//// [|x./**/|]
5+
//// }
6+
7+
const replacementSpan = test.ranges()[0]
8+
verify.completions({
9+
marker: "",
10+
includes: ["then"],
11+
excludes: ["trim"],
12+
preferences: {
13+
includeInsertTextCompletions: true,
14+
},
15+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
//// interface Foo { foo: string }
4+
//// async function foo(x: (a: number) => Promise<Foo>) {
5+
//// [|x(1)./**/|]
6+
//// }
7+
8+
const replacementSpan = test.ranges()[0]
9+
verify.completions({
10+
marker: "",
11+
includes: [
12+
"then",
13+
{ name: "foo", insertText: '(await x(1)).foo', replacementSpan },
14+
],
15+
preferences: {
16+
includeInsertTextCompletions: true,
17+
},
18+
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
//// async function foo(x: Promise<string>) {
4+
//// [|x./**/|]
5+
//// }
6+
7+
const replacementSpan = test.ranges()[0]
8+
verify.completions({
9+
marker: "",
10+
exact: [
11+
"then",
12+
"catch"
13+
],
14+
preferences: {
15+
includeInsertTextCompletions: false,
16+
},
17+
});

0 commit comments

Comments
 (0)