From d2fab65df691b998c5a1acb2fcbca9c25e493b8d Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Thu, 17 Oct 2019 17:04:45 -0700 Subject: [PATCH 1/8] Added test. --- src/compiler/types.ts | 1 + ...InsertQuestionDotWithUserPreferencesOff.ts | 20 +++++++++++++++++++ tests/cases/fourslash/fourslash.ts | 1 + 3 files changed, 22 insertions(+) create mode 100644 tests/cases/fourslash/completionNoAutoInsertQuestionDotWithUserPreferencesOff.ts diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 03b849b36c7..b444c9d1042 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -6441,6 +6441,7 @@ namespace ts { readonly disableSuggestions?: boolean; readonly quotePreference?: "auto" | "double" | "single"; readonly includeCompletionsForModuleExports?: boolean; + readonly includeAutomaticOptionalChainCompletions?: boolean; readonly includeCompletionsWithInsertText?: boolean; readonly importModuleSpecifierPreference?: "relative" | "non-relative"; /** Determines whether we import `foo/index.ts` as "foo", "foo/index", or "foo/index.js" */ diff --git a/tests/cases/fourslash/completionNoAutoInsertQuestionDotWithUserPreferencesOff.ts b/tests/cases/fourslash/completionNoAutoInsertQuestionDotWithUserPreferencesOff.ts new file mode 100644 index 00000000000..a1071a37f93 --- /dev/null +++ b/tests/cases/fourslash/completionNoAutoInsertQuestionDotWithUserPreferencesOff.ts @@ -0,0 +1,20 @@ +/// +// @strict: true + +//// interface User { +//// address?: { +//// city: string; +//// "postal code": string; +//// } +//// }; +//// declare const user: User; +//// user.address[|./**/|] + +verify.completions({ + marker: "", + exact: [], + preferences: { + includeInsertTextCompletions: true, + includeAutomaticOptionalChainCompletions: false + }, +}); diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 5672f430434..cd184df3bed 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -583,6 +583,7 @@ declare namespace FourSlashInterface { readonly quotePreference?: "double" | "single"; readonly includeCompletionsForModuleExports?: boolean; readonly includeInsertTextCompletions?: boolean; + readonly includeAutomaticOptionalChainCompletions?: boolean; readonly importModuleSpecifierPreference?: "relative" | "non-relative"; readonly importModuleSpecifierEnding?: "minimal" | "index" | "js"; } From 73e9715da5584b8be4001cdf5746e4d42516a57e Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Thu, 17 Oct 2019 17:05:08 -0700 Subject: [PATCH 2/8] Added option 'includeAutomaticOptionalChainCompletions' to disable '?.' completions. --- src/services/completions.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/services/completions.ts b/src/services/completions.ts index 11f92c3d4eb..75b0d3a1c1d 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -338,7 +338,12 @@ namespace ts.Completions { ): CompletionEntry | undefined { let insertText: string | undefined; let replacementSpan: TextSpan | undefined; + const insertQuestionDot = origin && originIsNullableMember(origin); + if (insertQuestionDot && preferences.includeAutomaticOptionalChainCompletions === false) { + return undefined; + } + const useBraces = origin && originIsSymbolMember(origin) || needsConvertPropertyAccess; if (origin && originIsThisType(origin)) { insertText = needsConvertPropertyAccess From 15445e156a8f26d7fa5db4fe90e510c66011d689 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Thu, 17 Oct 2019 17:17:19 -0700 Subject: [PATCH 3/8] Add user preference to the protocol. --- src/server/protocol.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/server/protocol.ts b/src/server/protocol.ts index c86876f1caa..fb0969fbefd 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -3007,6 +3007,12 @@ namespace ts.server.protocol { * For those entries, The `insertText` and `replacementSpan` properties will be set to change from `.x` property access to `["x"]`. */ readonly includeCompletionsWithInsertText?: boolean; + /** + * Unless this option is disabled, or `includeCompletionsWithInsertText` is not enabled, + * member completion lists triggered with `.` will include entries on potentially-null and potentially-undefined + * values, with insertion text to replace preceding `.` tokens with `?.`. + */ + readonly includeAutomaticOptionalChainCompletions?: boolean; readonly importModuleSpecifierPreference?: "relative" | "non-relative"; readonly allowTextChangesInNewFiles?: boolean; readonly lazyConfiguredProjectsFromExternalProject?: boolean; From 3d7451c08f98f6710414fe2c90c4a4d1d5709b06 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Thu, 17 Oct 2019 17:40:41 -0700 Subject: [PATCH 4/8] Accepted baselines. --- tests/baselines/reference/api/tsserverlibrary.d.ts | 7 +++++++ tests/baselines/reference/api/typescript.d.ts | 1 + 2 files changed, 8 insertions(+) diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index aa1945e74b9..9aef5232027 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -3171,6 +3171,7 @@ declare namespace ts { readonly disableSuggestions?: boolean; readonly quotePreference?: "auto" | "double" | "single"; readonly includeCompletionsForModuleExports?: boolean; + readonly includeAutomaticOptionalChainCompletions?: boolean; readonly includeCompletionsWithInsertText?: boolean; readonly importModuleSpecifierPreference?: "relative" | "non-relative"; /** Determines whether we import `foo/index.ts` as "foo", "foo/index", or "foo/index.js" */ @@ -8295,6 +8296,12 @@ declare namespace ts.server.protocol { * For those entries, The `insertText` and `replacementSpan` properties will be set to change from `.x` property access to `["x"]`. */ readonly includeCompletionsWithInsertText?: boolean; + /** + * Unless this option is disabled, or `includeCompletionsWithInsertText` is not enabled, + * member completion lists triggered with `.` will include entries on potentially-null and potentially-undefined + * values, with insertion text to replace preceding `.` tokens with `?.`. + */ + readonly includeAutomaticOptionalChainCompletions?: boolean; readonly importModuleSpecifierPreference?: "relative" | "non-relative"; readonly allowTextChangesInNewFiles?: boolean; readonly lazyConfiguredProjectsFromExternalProject?: boolean; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 7c756d8a0a3..7361ccb6768 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3171,6 +3171,7 @@ declare namespace ts { readonly disableSuggestions?: boolean; readonly quotePreference?: "auto" | "double" | "single"; readonly includeCompletionsForModuleExports?: boolean; + readonly includeAutomaticOptionalChainCompletions?: boolean; readonly includeCompletionsWithInsertText?: boolean; readonly importModuleSpecifierPreference?: "relative" | "non-relative"; /** Determines whether we import `foo/index.ts` as "foo", "foo/index", or "foo/index.js" */ From 85e09134be6fbd0077486274672d4f67748e0203 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Thu, 17 Oct 2019 17:45:13 -0700 Subject: [PATCH 5/8] Update comment. --- src/server/protocol.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/protocol.ts b/src/server/protocol.ts index fb0969fbefd..925e7638f9d 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -3008,7 +3008,7 @@ namespace ts.server.protocol { */ readonly includeCompletionsWithInsertText?: boolean; /** - * Unless this option is disabled, or `includeCompletionsWithInsertText` is not enabled, + * Unless this option is `false`, or `includeCompletionsWithInsertText` is not enabled, * member completion lists triggered with `.` will include entries on potentially-null and potentially-undefined * values, with insertion text to replace preceding `.` tokens with `?.`. */ From 30cca63eaf609261839aee091d36843acef5bb0f Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Thu, 17 Oct 2019 17:45:25 -0700 Subject: [PATCH 6/8] Accepted baselines. --- tests/baselines/reference/api/tsserverlibrary.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 9aef5232027..b02074e75ef 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -8297,7 +8297,7 @@ declare namespace ts.server.protocol { */ readonly includeCompletionsWithInsertText?: boolean; /** - * Unless this option is disabled, or `includeCompletionsWithInsertText` is not enabled, + * Unless this option is `false`, or `includeCompletionsWithInsertText` is not enabled, * member completion lists triggered with `.` will include entries on potentially-null and potentially-undefined * values, with insertion text to replace preceding `.` tokens with `?.`. */ From 9b6a02716798145a2edad7369178e02971fd9636 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Fri, 18 Oct 2019 13:03:25 -0700 Subject: [PATCH 7/8] Perform checks prior to calling `addTypeProperties`. --- src/services/completions.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index 75b0d3a1c1d..ac54cf9c69d 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -340,10 +340,6 @@ namespace ts.Completions { let replacementSpan: TextSpan | undefined; const insertQuestionDot = origin && originIsNullableMember(origin); - if (insertQuestionDot && preferences.includeAutomaticOptionalChainCompletions === false) { - return undefined; - } - const useBraces = origin && originIsSymbolMember(origin) || needsConvertPropertyAccess; if (origin && originIsThisType(origin)) { insertText = needsConvertPropertyAccess @@ -785,7 +781,7 @@ namespace ts.Completions { sourceFile: SourceFile, isUncheckedFile: boolean, position: number, - preferences: Pick, + preferences: Pick, detailsEntryId: CompletionEntryIdentifier | undefined, host: LanguageServiceHost, ): CompletionData | Request | undefined { @@ -1124,6 +1120,9 @@ namespace ts.Completions { insertQuestionDot = isRightOfDot && !isRightOfQuestionDot; type = type.getNonNullableType(); } + if (insertQuestionDot && preferences.includeAutomaticOptionalChainCompletions === false) { + return; + } addTypeProperties(type, !!(node.flags & NodeFlags.AwaitContext), insertQuestionDot); } @@ -1145,6 +1144,9 @@ namespace ts.Completions { insertQuestionDot = isRightOfDot && !isRightOfQuestionDot; type = type.getNonNullableType(); } + if (insertQuestionDot && preferences.includeAutomaticOptionalChainCompletions === false) { + return; + } addTypeProperties(type, !!(node.flags & NodeFlags.AwaitContext), insertQuestionDot); } } From 218bbcd669733b2df752501202730d8868480fac Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Fri, 18 Oct 2019 15:23:56 -0700 Subject: [PATCH 8/8] Don't immediately return in getMemberSymbols. --- src/services/completions.ts | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index ac54cf9c69d..076cc32f374 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1117,11 +1117,17 @@ namespace ts.Completions { let type = typeChecker.getTypeOfSymbolAtLocation(symbol, node).getNonOptionalType(); let insertQuestionDot = false; if (type.isNullableType()) { - insertQuestionDot = isRightOfDot && !isRightOfQuestionDot; - type = type.getNonNullableType(); - } - if (insertQuestionDot && preferences.includeAutomaticOptionalChainCompletions === false) { - return; + const canCorrectToQuestionDot = + isRightOfDot && + !isRightOfQuestionDot && + preferences.includeAutomaticOptionalChainCompletions !== false; + + if (canCorrectToQuestionDot || isRightOfQuestionDot) { + type = type.getNonNullableType(); + if (canCorrectToQuestionDot) { + insertQuestionDot = true; + } + } } addTypeProperties(type, !!(node.flags & NodeFlags.AwaitContext), insertQuestionDot); } @@ -1141,11 +1147,17 @@ namespace ts.Completions { let type = typeChecker.getTypeAtLocation(node).getNonOptionalType(); let insertQuestionDot = false; if (type.isNullableType()) { - insertQuestionDot = isRightOfDot && !isRightOfQuestionDot; - type = type.getNonNullableType(); - } - if (insertQuestionDot && preferences.includeAutomaticOptionalChainCompletions === false) { - return; + const canCorrectToQuestionDot = + isRightOfDot && + !isRightOfQuestionDot && + preferences.includeAutomaticOptionalChainCompletions !== false; + + if (canCorrectToQuestionDot || isRightOfQuestionDot) { + type = type.getNonNullableType(); + if (canCorrectToQuestionDot) { + insertQuestionDot = true; + } + } } addTypeProperties(type, !!(node.flags & NodeFlags.AwaitContext), insertQuestionDot); }