Skip to content

Commit b845800

Browse files
Add option to configure automatic optional chain completions (microsoft#34552)
Add option to configure automatic optional chain completions
2 parents f334654 + 218bbcd commit b845800

File tree

7 files changed

+60
-5
lines changed

7 files changed

+60
-5
lines changed

src/compiler/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -6452,6 +6452,7 @@ namespace ts {
64526452
readonly disableSuggestions?: boolean;
64536453
readonly quotePreference?: "auto" | "double" | "single";
64546454
readonly includeCompletionsForModuleExports?: boolean;
6455+
readonly includeAutomaticOptionalChainCompletions?: boolean;
64556456
readonly includeCompletionsWithInsertText?: boolean;
64566457
readonly importModuleSpecifierPreference?: "relative" | "non-relative";
64576458
/** Determines whether we import `foo/index.ts` as "foo", "foo/index", or "foo/index.js" */

src/server/protocol.ts

+6
Original file line numberDiff line numberDiff line change
@@ -3007,6 +3007,12 @@ namespace ts.server.protocol {
30073007
* For those entries, The `insertText` and `replacementSpan` properties will be set to change from `.x` property access to `["x"]`.
30083008
*/
30093009
readonly includeCompletionsWithInsertText?: boolean;
3010+
/**
3011+
* Unless this option is `false`, or `includeCompletionsWithInsertText` is not enabled,
3012+
* member completion lists triggered with `.` will include entries on potentially-null and potentially-undefined
3013+
* values, with insertion text to replace preceding `.` tokens with `?.`.
3014+
*/
3015+
readonly includeAutomaticOptionalChainCompletions?: boolean;
30103016
readonly importModuleSpecifierPreference?: "relative" | "non-relative";
30113017
readonly allowTextChangesInNewFiles?: boolean;
30123018
readonly lazyConfiguredProjectsFromExternalProject?: boolean;

src/services/completions.ts

+24-5
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ namespace ts.Completions {
338338
): CompletionEntry | undefined {
339339
let insertText: string | undefined;
340340
let replacementSpan: TextSpan | undefined;
341+
341342
const insertQuestionDot = origin && originIsNullableMember(origin);
342343
const useBraces = origin && originIsSymbolMember(origin) || needsConvertPropertyAccess;
343344
if (origin && originIsThisType(origin)) {
@@ -780,7 +781,7 @@ namespace ts.Completions {
780781
sourceFile: SourceFile,
781782
isUncheckedFile: boolean,
782783
position: number,
783-
preferences: Pick<UserPreferences, "includeCompletionsForModuleExports" | "includeCompletionsWithInsertText">,
784+
preferences: Pick<UserPreferences, "includeCompletionsForModuleExports" | "includeCompletionsWithInsertText" | "includeAutomaticOptionalChainCompletions">,
784785
detailsEntryId: CompletionEntryIdentifier | undefined,
785786
host: LanguageServiceHost,
786787
): CompletionData | Request | undefined {
@@ -1116,8 +1117,17 @@ namespace ts.Completions {
11161117
let type = typeChecker.getTypeOfSymbolAtLocation(symbol, node).getNonOptionalType();
11171118
let insertQuestionDot = false;
11181119
if (type.isNullableType()) {
1119-
insertQuestionDot = isRightOfDot && !isRightOfQuestionDot;
1120-
type = type.getNonNullableType();
1120+
const canCorrectToQuestionDot =
1121+
isRightOfDot &&
1122+
!isRightOfQuestionDot &&
1123+
preferences.includeAutomaticOptionalChainCompletions !== false;
1124+
1125+
if (canCorrectToQuestionDot || isRightOfQuestionDot) {
1126+
type = type.getNonNullableType();
1127+
if (canCorrectToQuestionDot) {
1128+
insertQuestionDot = true;
1129+
}
1130+
}
11211131
}
11221132
addTypeProperties(type, !!(node.flags & NodeFlags.AwaitContext), insertQuestionDot);
11231133
}
@@ -1137,8 +1147,17 @@ namespace ts.Completions {
11371147
let type = typeChecker.getTypeAtLocation(node).getNonOptionalType();
11381148
let insertQuestionDot = false;
11391149
if (type.isNullableType()) {
1140-
insertQuestionDot = isRightOfDot && !isRightOfQuestionDot;
1141-
type = type.getNonNullableType();
1150+
const canCorrectToQuestionDot =
1151+
isRightOfDot &&
1152+
!isRightOfQuestionDot &&
1153+
preferences.includeAutomaticOptionalChainCompletions !== false;
1154+
1155+
if (canCorrectToQuestionDot || isRightOfQuestionDot) {
1156+
type = type.getNonNullableType();
1157+
if (canCorrectToQuestionDot) {
1158+
insertQuestionDot = true;
1159+
}
1160+
}
11421161
}
11431162
addTypeProperties(type, !!(node.flags & NodeFlags.AwaitContext), insertQuestionDot);
11441163
}

tests/baselines/reference/api/tsserverlibrary.d.ts

+7
Original file line numberDiff line numberDiff line change
@@ -3171,6 +3171,7 @@ declare namespace ts {
31713171
readonly disableSuggestions?: boolean;
31723172
readonly quotePreference?: "auto" | "double" | "single";
31733173
readonly includeCompletionsForModuleExports?: boolean;
3174+
readonly includeAutomaticOptionalChainCompletions?: boolean;
31743175
readonly includeCompletionsWithInsertText?: boolean;
31753176
readonly importModuleSpecifierPreference?: "relative" | "non-relative";
31763177
/** Determines whether we import `foo/index.ts` as "foo", "foo/index", or "foo/index.js" */
@@ -8295,6 +8296,12 @@ declare namespace ts.server.protocol {
82958296
* For those entries, The `insertText` and `replacementSpan` properties will be set to change from `.x` property access to `["x"]`.
82968297
*/
82978298
readonly includeCompletionsWithInsertText?: boolean;
8299+
/**
8300+
* Unless this option is `false`, or `includeCompletionsWithInsertText` is not enabled,
8301+
* member completion lists triggered with `.` will include entries on potentially-null and potentially-undefined
8302+
* values, with insertion text to replace preceding `.` tokens with `?.`.
8303+
*/
8304+
readonly includeAutomaticOptionalChainCompletions?: boolean;
82988305
readonly importModuleSpecifierPreference?: "relative" | "non-relative";
82998306
readonly allowTextChangesInNewFiles?: boolean;
83008307
readonly lazyConfiguredProjectsFromExternalProject?: boolean;

tests/baselines/reference/api/typescript.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -3171,6 +3171,7 @@ declare namespace ts {
31713171
readonly disableSuggestions?: boolean;
31723172
readonly quotePreference?: "auto" | "double" | "single";
31733173
readonly includeCompletionsForModuleExports?: boolean;
3174+
readonly includeAutomaticOptionalChainCompletions?: boolean;
31743175
readonly includeCompletionsWithInsertText?: boolean;
31753176
readonly importModuleSpecifierPreference?: "relative" | "non-relative";
31763177
/** Determines whether we import `foo/index.ts` as "foo", "foo/index", or "foo/index.js" */
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/// <reference path="fourslash.ts" />
2+
// @strict: true
3+
4+
//// interface User {
5+
//// address?: {
6+
//// city: string;
7+
//// "postal code": string;
8+
//// }
9+
//// };
10+
//// declare const user: User;
11+
//// user.address[|./**/|]
12+
13+
verify.completions({
14+
marker: "",
15+
exact: [],
16+
preferences: {
17+
includeInsertTextCompletions: true,
18+
includeAutomaticOptionalChainCompletions: false
19+
},
20+
});

tests/cases/fourslash/fourslash.ts

+1
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,7 @@ declare namespace FourSlashInterface {
583583
readonly quotePreference?: "double" | "single";
584584
readonly includeCompletionsForModuleExports?: boolean;
585585
readonly includeInsertTextCompletions?: boolean;
586+
readonly includeAutomaticOptionalChainCompletions?: boolean;
586587
readonly importModuleSpecifierPreference?: "relative" | "non-relative";
587588
readonly importModuleSpecifierEnding?: "minimal" | "index" | "js";
588589
}

0 commit comments

Comments
 (0)