From 318b59f1f61a3d34518b08ce05941ceb4921d9c3 Mon Sep 17 00:00:00 2001 From: Armando Aguirre Date: Wed, 15 Sep 2021 21:29:18 -0700 Subject: [PATCH 1/5] Added Jsx completion feature and tests --- src/compiler/types.ts | 1 + src/server/protocol.ts | 1 + src/services/completions.ts | 33 ++++++- .../reference/api/tsserverlibrary.d.ts | 2 + tests/baselines/reference/api/typescript.d.ts | 1 + tests/cases/fourslash/fourslash.ts | 1 + .../jsxAttributeSnippetCompletionAuto.ts | 88 +++++++++++++++++ .../jsxAttributeSnippetCompletionBraces.ts | 94 +++++++++++++++++++ .../jsxAttributeSnippetCompletionDefault.ts | 74 +++++++++++++++ .../jsxAttributeSnippetCompletionNone.ts | 74 +++++++++++++++ 10 files changed, 368 insertions(+), 1 deletion(-) create mode 100644 tests/cases/fourslash/jsxAttributeSnippetCompletionAuto.ts create mode 100644 tests/cases/fourslash/jsxAttributeSnippetCompletionBraces.ts create mode 100644 tests/cases/fourslash/jsxAttributeSnippetCompletionDefault.ts create mode 100644 tests/cases/fourslash/jsxAttributeSnippetCompletionNone.ts diff --git a/src/compiler/types.ts b/src/compiler/types.ts index ae1e7c16d00d2..f86978f689021 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -8547,6 +8547,7 @@ namespace ts { readonly providePrefixAndSuffixTextForRename?: boolean; readonly includePackageJsonAutoImports?: "auto" | "on" | "off"; readonly provideRefactorNotApplicableReason?: boolean; + readonly jsxSnippetCompletion?: "auto" | "braces" | "none"; } /** Represents a bigint literal value without requiring bigint support */ diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 6147077051b52..f181f890105a2 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -3391,6 +3391,7 @@ namespace ts.server.protocol { readonly provideRefactorNotApplicableReason?: boolean; readonly allowRenameOfImportPath?: boolean; readonly includePackageJsonAutoImports?: "auto" | "on" | "off"; + readonly jsxSnippetCompletion?: "auto" | "braces" | "none"; readonly displayPartsForJSDoc?: boolean; readonly generateReturnInDocTemplate?: boolean; diff --git a/src/services/completions.ts b/src/services/completions.ts index ad5e24ea3ebfb..29dbe1f61b01e 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -675,6 +675,37 @@ namespace ts.Completions { hasAction = !importCompletionNode; } + const kind = SymbolDisplay.getSymbolKind(typeChecker, symbol, location); // TODO: GH#18217 + if (kind === ScriptElementKind.jsxAttribute && preferences.jsxSnippetCompletion && preferences.jsxSnippetCompletion !== "none") { + let useBraces = preferences.jsxSnippetCompletion === "braces"; + const type = typeChecker.getTypeOfSymbolAtLocation(symbol, location); + + // If is boolean like or undefined, don't return a snippet we want just to return the completion. + if (preferences.jsxSnippetCompletion === "auto" + && !(type.flags & TypeFlags.BooleanLike) + && !(type.flags & TypeFlags.Union && every((type as UnionType).types, type => !!(type.flags & (TypeFlags.BooleanLike | TypeFlags.Undefined)))) + ) { + if (type.flags & TypeFlags.StringLike || (type.flags & TypeFlags.Union && every((type as UnionType).types, type => !!(type.flags & (TypeFlags.StringLike | TypeFlags.Undefined))))) { + // If is string like or undefined use quotes + insertText = `${name}=${quote(sourceFile, preferences, "$1")}`; + isSnippet = true; + } + else { + // Use braces for everything else + useBraces = true; + } + } + + if (useBraces) { + insertText = `${name}={$1}`; + isSnippet = true; + } + + if (isSnippet) { + replacementSpan = createTextSpanFromNode(location, sourceFile); + } + } + // TODO(drosen): Right now we just permit *all* semantic meanings when calling // 'getSymbolKind' which is permissible given that it is backwards compatible; but // really we should consider passing the meaning for the node so that we don't report @@ -685,7 +716,7 @@ namespace ts.Completions { // entries (like JavaScript identifier entries). return { name, - kind: SymbolDisplay.getSymbolKind(typeChecker, symbol, location), // TODO: GH#18217 + kind, kindModifiers: SymbolDisplay.getSymbolModifiers(typeChecker, symbol), sortText, source: getSourceFromOrigin(origin), diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index f23a441f98c01..4349dbb3ffedd 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -3995,6 +3995,7 @@ declare namespace ts { readonly providePrefixAndSuffixTextForRename?: boolean; readonly includePackageJsonAutoImports?: "auto" | "on" | "off"; readonly provideRefactorNotApplicableReason?: boolean; + readonly jsxSnippetCompletion?: "auto" | "braces" | "none"; } /** Represents a bigint literal value without requiring bigint support */ export interface PseudoBigInt { @@ -9458,6 +9459,7 @@ declare namespace ts.server.protocol { readonly provideRefactorNotApplicableReason?: boolean; readonly allowRenameOfImportPath?: boolean; readonly includePackageJsonAutoImports?: "auto" | "on" | "off"; + readonly jsxSnippetCompletion?: "auto" | "braces" | "none"; readonly displayPartsForJSDoc?: boolean; readonly generateReturnInDocTemplate?: boolean; } diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 8a9603b7b7cae..18145d5ca78ee 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3995,6 +3995,7 @@ declare namespace ts { readonly providePrefixAndSuffixTextForRename?: boolean; readonly includePackageJsonAutoImports?: "auto" | "on" | "off"; readonly provideRefactorNotApplicableReason?: boolean; + readonly jsxSnippetCompletion?: "auto" | "braces" | "none"; } /** Represents a bigint literal value without requiring bigint support */ export interface PseudoBigInt { diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 273adfb9707fe..5187b77918aa4 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -658,6 +658,7 @@ declare namespace FourSlashInterface { readonly includeAutomaticOptionalChainCompletions?: boolean; readonly importModuleSpecifierPreference?: "shortest" | "project-relative" | "relative" | "non-relative"; readonly importModuleSpecifierEnding?: "minimal" | "index" | "js"; + readonly jsxSnippetCompletion?: "auto" | "braces" | "none"; } interface InlayHintsOptions extends UserPreferences { readonly includeInlayParameterNameHints?: "none" | "literals" | "all"; diff --git a/tests/cases/fourslash/jsxAttributeSnippetCompletionAuto.ts b/tests/cases/fourslash/jsxAttributeSnippetCompletionAuto.ts new file mode 100644 index 0000000000000..e42b747e533fb --- /dev/null +++ b/tests/cases/fourslash/jsxAttributeSnippetCompletionAuto.ts @@ -0,0 +1,88 @@ +/// + +// @Filename: foo.tsx +//// declare namespace JSX { +//// interface Element { } +//// interface IntrinsicElements { +//// foo: { +//// prop_a: boolean; +//// prop_b: string; +//// prop_c: any; +//// prop_d: { p1: string; } +//// prop_e: string | undefined; +//// prop_f: boolean | undefined; +//// prop_g: { p1: string; } | undefined; +//// prop_h?: string; +//// prop_i?: boolean; +//// prop_j?: { p1: string; }; +//// } +//// } +//// } +//// +//// + +verify.completions({ + marker: "", + exact: [ + { + name: "prop_a", + isSnippet: undefined, + }, + { + name: "prop_b", + insertText: "prop_b=\"$1\"", + replacementSpan: test.ranges()[0], + isSnippet: true, + }, + { + name: "prop_c", + insertText: "prop_c={$1}", + replacementSpan: test.ranges()[0], + isSnippet: true, + }, + { + name: "prop_d", + insertText: "prop_d={$1}", + replacementSpan: test.ranges()[0], + isSnippet: true, + }, + { + name: "prop_e", + insertText: "prop_e=\"$1\"", + replacementSpan: test.ranges()[0], + isSnippet: true, + }, + { + name: "prop_f", + isSnippet: undefined, + }, + { + name: "prop_g", + insertText: "prop_g={$1}", + replacementSpan: test.ranges()[0], + isSnippet: true, + }, + { + name: "prop_h", + insertText: "prop_h=\"$1\"", + replacementSpan: test.ranges()[0], + isSnippet: true, + sortText: completion.SortText.OptionalMember, + }, + { + name: "prop_i", + isSnippet: undefined, + sortText: completion.SortText.OptionalMember, + }, + { + name: "prop_j", + insertText: "prop_j={$1}", + replacementSpan: test.ranges()[0], + isSnippet: true, + sortText: completion.SortText.OptionalMember, + } + ], + preferences: { + jsxSnippetCompletion: "auto" + } +}); \ No newline at end of file diff --git a/tests/cases/fourslash/jsxAttributeSnippetCompletionBraces.ts b/tests/cases/fourslash/jsxAttributeSnippetCompletionBraces.ts new file mode 100644 index 0000000000000..1ff939fa70233 --- /dev/null +++ b/tests/cases/fourslash/jsxAttributeSnippetCompletionBraces.ts @@ -0,0 +1,94 @@ +/// + +// @Filename: foo.tsx +//// declare namespace JSX { +//// interface Element { } +//// interface IntrinsicElements { +//// foo: { +//// prop_a: boolean; +//// prop_b: string; +//// prop_c: any; +//// prop_d: { p1: string; } +//// prop_e: string | undefined; +//// prop_f: boolean | undefined; +//// prop_g: { p1: string; } | undefined; +//// prop_h?: string; +//// prop_i?: boolean; +//// prop_j?: { p1: string; }; +//// } +//// } +//// } +//// +//// + +verify.completions({ + marker: "", + exact: [ + { + name: "prop_a", + insertText: "prop_a={$1}", + replacementSpan: test.ranges()[0], + isSnippet: true, + }, + { + name: "prop_b", + insertText: "prop_b={$1}", + replacementSpan: test.ranges()[0], + isSnippet: true, + }, + { + name: "prop_c", + insertText: "prop_c={$1}", + replacementSpan: test.ranges()[0], + isSnippet: true, + }, + { + name: "prop_d", + insertText: "prop_d={$1}", + replacementSpan: test.ranges()[0], + isSnippet: true, + }, + { + name: "prop_e", + insertText: "prop_e={$1}", + replacementSpan: test.ranges()[0], + isSnippet: true, + }, + { + name: "prop_f", + insertText: "prop_f={$1}", + replacementSpan: test.ranges()[0], + isSnippet: true, + }, + { + name: "prop_g", + insertText: "prop_g={$1}", + replacementSpan: test.ranges()[0], + isSnippet: true, + }, + { + name: "prop_h", + insertText: "prop_h={$1}", + replacementSpan: test.ranges()[0], + isSnippet: true, + sortText: completion.SortText.OptionalMember, + }, + { + name: "prop_i", + insertText: "prop_i={$1}", + replacementSpan: test.ranges()[0], + isSnippet: true, + sortText: completion.SortText.OptionalMember, + }, + { + name: "prop_j", + insertText: "prop_j={$1}", + replacementSpan: test.ranges()[0], + isSnippet: true, + sortText: completion.SortText.OptionalMember, + } + ], + preferences: { + jsxSnippetCompletion: "braces" + } +}); \ No newline at end of file diff --git a/tests/cases/fourslash/jsxAttributeSnippetCompletionDefault.ts b/tests/cases/fourslash/jsxAttributeSnippetCompletionDefault.ts new file mode 100644 index 0000000000000..40527a1916982 --- /dev/null +++ b/tests/cases/fourslash/jsxAttributeSnippetCompletionDefault.ts @@ -0,0 +1,74 @@ +/// + +// @Filename: foo.tsx +//// declare namespace JSX { +//// interface Element { } +//// interface IntrinsicElements { +//// foo: { +//// prop_a: boolean; +//// prop_b: string; +//// prop_c: any; +//// prop_d: { p1: string; } +//// prop_e: string | undefined; +//// prop_f: boolean | undefined; +//// prop_g: { p1: string; } | undefined; +//// prop_h?: string; +//// prop_i?: boolean; +//// prop_j?: { p1: string; }; +//// } +//// } +//// } +//// +//// + +verify.completions({ + marker: "", + exact: [ + { + name: "prop_a", + isSnippet: undefined, + }, + { + name: "prop_b", + isSnippet: undefined, + }, + { + name: "prop_c", + isSnippet: undefined, + }, + { + name: "prop_d", + isSnippet: undefined, + }, + { + name: "prop_e", + isSnippet: undefined, + }, + { + name: "prop_f", + isSnippet: undefined, + }, + { + name: "prop_g", + isSnippet: undefined, + }, + { + name: "prop_h", + isSnippet: undefined, + sortText: completion.SortText.OptionalMember, + }, + { + name: "prop_i", + isSnippet: undefined, + sortText: completion.SortText.OptionalMember, + }, + { + name: "prop_j", + isSnippet: undefined, + sortText: completion.SortText.OptionalMember, + } + ], + preferences: { + jsxSnippetCompletion: undefined + } +}); \ No newline at end of file diff --git a/tests/cases/fourslash/jsxAttributeSnippetCompletionNone.ts b/tests/cases/fourslash/jsxAttributeSnippetCompletionNone.ts new file mode 100644 index 0000000000000..46e5e0339a500 --- /dev/null +++ b/tests/cases/fourslash/jsxAttributeSnippetCompletionNone.ts @@ -0,0 +1,74 @@ +/// + +// @Filename: foo.tsx +//// declare namespace JSX { +//// interface Element { } +//// interface IntrinsicElements { +//// foo: { +//// prop_a: boolean; +//// prop_b: string; +//// prop_c: any; +//// prop_d: { p1: string; } +//// prop_e: string | undefined; +//// prop_f: boolean | undefined; +//// prop_g: { p1: string; } | undefined; +//// prop_h?: string; +//// prop_i?: boolean; +//// prop_j?: { p1: string; }; +//// } +//// } +//// } +//// +//// + +verify.completions({ + marker: "", + exact: [ + { + name: "prop_a", + isSnippet: undefined, + }, + { + name: "prop_b", + isSnippet: undefined, + }, + { + name: "prop_c", + isSnippet: undefined, + }, + { + name: "prop_d", + isSnippet: undefined, + }, + { + name: "prop_e", + isSnippet: undefined, + }, + { + name: "prop_f", + isSnippet: undefined, + }, + { + name: "prop_g", + isSnippet: undefined, + }, + { + name: "prop_h", + isSnippet: undefined, + sortText: completion.SortText.OptionalMember, + }, + { + name: "prop_i", + isSnippet: undefined, + sortText: completion.SortText.OptionalMember, + }, + { + name: "prop_j", + isSnippet: undefined, + sortText: completion.SortText.OptionalMember, + } + ], + preferences: { + jsxSnippetCompletion: "none" + } +}); \ No newline at end of file From 85f9b3d35aee483a7803fa77e0175dff487aef51 Mon Sep 17 00:00:00 2001 From: Armando Aguirre Date: Fri, 17 Sep 2021 14:26:45 -0700 Subject: [PATCH 2/5] Renamed jsxSnippetCompletion to jsxAttributeCompletionStyle --- src/compiler/types.ts | 2 +- src/server/protocol.ts | 2 +- src/services/completions.ts | 6 +++--- tests/baselines/reference/api/tsserverlibrary.d.ts | 4 ++-- tests/baselines/reference/api/typescript.d.ts | 2 +- tests/cases/fourslash/fourslash.ts | 2 +- tests/cases/fourslash/jsxAttributeSnippetCompletionAuto.ts | 2 +- .../cases/fourslash/jsxAttributeSnippetCompletionBraces.ts | 2 +- .../cases/fourslash/jsxAttributeSnippetCompletionDefault.ts | 2 +- tests/cases/fourslash/jsxAttributeSnippetCompletionNone.ts | 2 +- 10 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index f86978f689021..00983e6745275 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -8547,7 +8547,7 @@ namespace ts { readonly providePrefixAndSuffixTextForRename?: boolean; readonly includePackageJsonAutoImports?: "auto" | "on" | "off"; readonly provideRefactorNotApplicableReason?: boolean; - readonly jsxSnippetCompletion?: "auto" | "braces" | "none"; + readonly jsxAttributeCompletionStyle?: "auto" | "braces" | "none"; } /** Represents a bigint literal value without requiring bigint support */ diff --git a/src/server/protocol.ts b/src/server/protocol.ts index f181f890105a2..67d6fda2a3b1b 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -3391,7 +3391,7 @@ namespace ts.server.protocol { readonly provideRefactorNotApplicableReason?: boolean; readonly allowRenameOfImportPath?: boolean; readonly includePackageJsonAutoImports?: "auto" | "on" | "off"; - readonly jsxSnippetCompletion?: "auto" | "braces" | "none"; + readonly jsxAttributeCompletionStyle?: "auto" | "braces" | "none"; readonly displayPartsForJSDoc?: boolean; readonly generateReturnInDocTemplate?: boolean; diff --git a/src/services/completions.ts b/src/services/completions.ts index 29dbe1f61b01e..ccbc34b851413 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -676,12 +676,12 @@ namespace ts.Completions { } const kind = SymbolDisplay.getSymbolKind(typeChecker, symbol, location); // TODO: GH#18217 - if (kind === ScriptElementKind.jsxAttribute && preferences.jsxSnippetCompletion && preferences.jsxSnippetCompletion !== "none") { - let useBraces = preferences.jsxSnippetCompletion === "braces"; + if (kind === ScriptElementKind.jsxAttribute && preferences.includeCompletionsWithSnippetText && preferences.jsxAttributeCompletionStyle && preferences.jsxAttributeCompletionStyle !== "none") { + let useBraces = preferences.jsxAttributeCompletionStyle === "braces"; const type = typeChecker.getTypeOfSymbolAtLocation(symbol, location); // If is boolean like or undefined, don't return a snippet we want just to return the completion. - if (preferences.jsxSnippetCompletion === "auto" + if (preferences.jsxAttributeCompletionStyle === "auto" && !(type.flags & TypeFlags.BooleanLike) && !(type.flags & TypeFlags.Union && every((type as UnionType).types, type => !!(type.flags & (TypeFlags.BooleanLike | TypeFlags.Undefined)))) ) { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 4349dbb3ffedd..3387aa55c1768 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -3995,7 +3995,7 @@ declare namespace ts { readonly providePrefixAndSuffixTextForRename?: boolean; readonly includePackageJsonAutoImports?: "auto" | "on" | "off"; readonly provideRefactorNotApplicableReason?: boolean; - readonly jsxSnippetCompletion?: "auto" | "braces" | "none"; + readonly jsxAttributeCompletionStyle?: "auto" | "braces" | "none"; } /** Represents a bigint literal value without requiring bigint support */ export interface PseudoBigInt { @@ -9459,7 +9459,7 @@ declare namespace ts.server.protocol { readonly provideRefactorNotApplicableReason?: boolean; readonly allowRenameOfImportPath?: boolean; readonly includePackageJsonAutoImports?: "auto" | "on" | "off"; - readonly jsxSnippetCompletion?: "auto" | "braces" | "none"; + readonly jsxAttributeCompletionStyle?: "auto" | "braces" | "none"; readonly displayPartsForJSDoc?: boolean; readonly generateReturnInDocTemplate?: boolean; } diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 18145d5ca78ee..40950e13fd321 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3995,7 +3995,7 @@ declare namespace ts { readonly providePrefixAndSuffixTextForRename?: boolean; readonly includePackageJsonAutoImports?: "auto" | "on" | "off"; readonly provideRefactorNotApplicableReason?: boolean; - readonly jsxSnippetCompletion?: "auto" | "braces" | "none"; + readonly jsxAttributeCompletionStyle?: "auto" | "braces" | "none"; } /** Represents a bigint literal value without requiring bigint support */ export interface PseudoBigInt { diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 5187b77918aa4..01a1a4a4c0a47 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -658,7 +658,7 @@ declare namespace FourSlashInterface { readonly includeAutomaticOptionalChainCompletions?: boolean; readonly importModuleSpecifierPreference?: "shortest" | "project-relative" | "relative" | "non-relative"; readonly importModuleSpecifierEnding?: "minimal" | "index" | "js"; - readonly jsxSnippetCompletion?: "auto" | "braces" | "none"; + readonly jsxAttributeCompletionStyle?: "auto" | "braces" | "none"; } interface InlayHintsOptions extends UserPreferences { readonly includeInlayParameterNameHints?: "none" | "literals" | "all"; diff --git a/tests/cases/fourslash/jsxAttributeSnippetCompletionAuto.ts b/tests/cases/fourslash/jsxAttributeSnippetCompletionAuto.ts index e42b747e533fb..d016a53825023 100644 --- a/tests/cases/fourslash/jsxAttributeSnippetCompletionAuto.ts +++ b/tests/cases/fourslash/jsxAttributeSnippetCompletionAuto.ts @@ -83,6 +83,6 @@ verify.completions({ } ], preferences: { - jsxSnippetCompletion: "auto" + jsxAttributeCompletionStyle: "auto" } }); \ No newline at end of file diff --git a/tests/cases/fourslash/jsxAttributeSnippetCompletionBraces.ts b/tests/cases/fourslash/jsxAttributeSnippetCompletionBraces.ts index 1ff939fa70233..139b862ef949d 100644 --- a/tests/cases/fourslash/jsxAttributeSnippetCompletionBraces.ts +++ b/tests/cases/fourslash/jsxAttributeSnippetCompletionBraces.ts @@ -89,6 +89,6 @@ verify.completions({ } ], preferences: { - jsxSnippetCompletion: "braces" + jsxAttributeCompletionStyle: "braces" } }); \ No newline at end of file diff --git a/tests/cases/fourslash/jsxAttributeSnippetCompletionDefault.ts b/tests/cases/fourslash/jsxAttributeSnippetCompletionDefault.ts index 40527a1916982..e037a8762213d 100644 --- a/tests/cases/fourslash/jsxAttributeSnippetCompletionDefault.ts +++ b/tests/cases/fourslash/jsxAttributeSnippetCompletionDefault.ts @@ -69,6 +69,6 @@ verify.completions({ } ], preferences: { - jsxSnippetCompletion: undefined + jsxAttributeCompletionStyle: undefined } }); \ No newline at end of file diff --git a/tests/cases/fourslash/jsxAttributeSnippetCompletionNone.ts b/tests/cases/fourslash/jsxAttributeSnippetCompletionNone.ts index 46e5e0339a500..67e52cfeb30e9 100644 --- a/tests/cases/fourslash/jsxAttributeSnippetCompletionNone.ts +++ b/tests/cases/fourslash/jsxAttributeSnippetCompletionNone.ts @@ -69,6 +69,6 @@ verify.completions({ } ], preferences: { - jsxSnippetCompletion: "none" + jsxAttributeCompletionStyle: "none" } }); \ No newline at end of file From 48e4dc8b6af1d993eaa9d9255a43ea1b3ab2fab9 Mon Sep 17 00:00:00 2001 From: Armando Aguirre Date: Fri, 17 Sep 2021 14:29:08 -0700 Subject: [PATCH 3/5] Renamed tests files --- ...nippetCompletionAuto.ts => jsxAttributeCompletionStyleAuto.ts} | 0 ...etCompletionBraces.ts => jsxAttributeCompletionStyleBraces.ts} | 0 ...CompletionDefault.ts => jsxAttributeCompletionStyleDefault.ts} | 0 ...nippetCompletionNone.ts => jsxAttributeCompletionStyleNone.ts} | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename tests/cases/fourslash/{jsxAttributeSnippetCompletionAuto.ts => jsxAttributeCompletionStyleAuto.ts} (100%) rename tests/cases/fourslash/{jsxAttributeSnippetCompletionBraces.ts => jsxAttributeCompletionStyleBraces.ts} (100%) rename tests/cases/fourslash/{jsxAttributeSnippetCompletionDefault.ts => jsxAttributeCompletionStyleDefault.ts} (100%) rename tests/cases/fourslash/{jsxAttributeSnippetCompletionNone.ts => jsxAttributeCompletionStyleNone.ts} (100%) diff --git a/tests/cases/fourslash/jsxAttributeSnippetCompletionAuto.ts b/tests/cases/fourslash/jsxAttributeCompletionStyleAuto.ts similarity index 100% rename from tests/cases/fourslash/jsxAttributeSnippetCompletionAuto.ts rename to tests/cases/fourslash/jsxAttributeCompletionStyleAuto.ts diff --git a/tests/cases/fourslash/jsxAttributeSnippetCompletionBraces.ts b/tests/cases/fourslash/jsxAttributeCompletionStyleBraces.ts similarity index 100% rename from tests/cases/fourslash/jsxAttributeSnippetCompletionBraces.ts rename to tests/cases/fourslash/jsxAttributeCompletionStyleBraces.ts diff --git a/tests/cases/fourslash/jsxAttributeSnippetCompletionDefault.ts b/tests/cases/fourslash/jsxAttributeCompletionStyleDefault.ts similarity index 100% rename from tests/cases/fourslash/jsxAttributeSnippetCompletionDefault.ts rename to tests/cases/fourslash/jsxAttributeCompletionStyleDefault.ts diff --git a/tests/cases/fourslash/jsxAttributeSnippetCompletionNone.ts b/tests/cases/fourslash/jsxAttributeCompletionStyleNone.ts similarity index 100% rename from tests/cases/fourslash/jsxAttributeSnippetCompletionNone.ts rename to tests/cases/fourslash/jsxAttributeCompletionStyleNone.ts From b0c7719a50c89abae84dd52ac397c3da869f7aa3 Mon Sep 17 00:00:00 2001 From: Armando Aguirre Date: Fri, 17 Sep 2021 14:55:05 -0700 Subject: [PATCH 4/5] Changed boolean filter --- src/services/completions.ts | 2 +- .../jsxAttributeCompletionStyleAuto.ts | 5 +- .../jsxAttributeCompletionStyleBraces.ts | 5 +- .../jsxAttributeCompletionStyleDefault.ts | 5 +- .../jsxAttributeCompletionStyleNoSnippet.ts | 75 +++++++++++++++++++ .../jsxAttributeCompletionStyleNone.ts | 5 +- 6 files changed, 88 insertions(+), 9 deletions(-) create mode 100644 tests/cases/fourslash/jsxAttributeCompletionStyleNoSnippet.ts diff --git a/src/services/completions.ts b/src/services/completions.ts index ccbc34b851413..9724457796227 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -683,7 +683,7 @@ namespace ts.Completions { // If is boolean like or undefined, don't return a snippet we want just to return the completion. if (preferences.jsxAttributeCompletionStyle === "auto" && !(type.flags & TypeFlags.BooleanLike) - && !(type.flags & TypeFlags.Union && every((type as UnionType).types, type => !!(type.flags & (TypeFlags.BooleanLike | TypeFlags.Undefined)))) + && !(type.flags & TypeFlags.Union && find((type as UnionType).types, type => !!(type.flags & TypeFlags.BooleanLike))) ) { if (type.flags & TypeFlags.StringLike || (type.flags & TypeFlags.Union && every((type as UnionType).types, type => !!(type.flags & (TypeFlags.StringLike | TypeFlags.Undefined))))) { // If is string like or undefined use quotes diff --git a/tests/cases/fourslash/jsxAttributeCompletionStyleAuto.ts b/tests/cases/fourslash/jsxAttributeCompletionStyleAuto.ts index d016a53825023..dc7463df4b407 100644 --- a/tests/cases/fourslash/jsxAttributeCompletionStyleAuto.ts +++ b/tests/cases/fourslash/jsxAttributeCompletionStyleAuto.ts @@ -10,7 +10,7 @@ //// prop_c: any; //// prop_d: { p1: string; } //// prop_e: string | undefined; -//// prop_f: boolean | undefined; +//// prop_f: boolean | undefined | { p1: string; }; //// prop_g: { p1: string; } | undefined; //// prop_h?: string; //// prop_i?: boolean; @@ -83,6 +83,7 @@ verify.completions({ } ], preferences: { - jsxAttributeCompletionStyle: "auto" + jsxAttributeCompletionStyle: "auto", + includeCompletionsWithSnippetText: true } }); \ No newline at end of file diff --git a/tests/cases/fourslash/jsxAttributeCompletionStyleBraces.ts b/tests/cases/fourslash/jsxAttributeCompletionStyleBraces.ts index 139b862ef949d..051e1bf410d18 100644 --- a/tests/cases/fourslash/jsxAttributeCompletionStyleBraces.ts +++ b/tests/cases/fourslash/jsxAttributeCompletionStyleBraces.ts @@ -10,7 +10,7 @@ //// prop_c: any; //// prop_d: { p1: string; } //// prop_e: string | undefined; -//// prop_f: boolean | undefined; +//// prop_f: boolean | undefined | { p1: string; }; //// prop_g: { p1: string; } | undefined; //// prop_h?: string; //// prop_i?: boolean; @@ -89,6 +89,7 @@ verify.completions({ } ], preferences: { - jsxAttributeCompletionStyle: "braces" + jsxAttributeCompletionStyle: "braces", + includeCompletionsWithSnippetText: true } }); \ No newline at end of file diff --git a/tests/cases/fourslash/jsxAttributeCompletionStyleDefault.ts b/tests/cases/fourslash/jsxAttributeCompletionStyleDefault.ts index e037a8762213d..134721cc67e09 100644 --- a/tests/cases/fourslash/jsxAttributeCompletionStyleDefault.ts +++ b/tests/cases/fourslash/jsxAttributeCompletionStyleDefault.ts @@ -10,7 +10,7 @@ //// prop_c: any; //// prop_d: { p1: string; } //// prop_e: string | undefined; -//// prop_f: boolean | undefined; +//// prop_f: boolean | undefined | { p1: string; }; //// prop_g: { p1: string; } | undefined; //// prop_h?: string; //// prop_i?: boolean; @@ -69,6 +69,7 @@ verify.completions({ } ], preferences: { - jsxAttributeCompletionStyle: undefined + jsxAttributeCompletionStyle: undefined, + includeCompletionsWithSnippetText: true } }); \ No newline at end of file diff --git a/tests/cases/fourslash/jsxAttributeCompletionStyleNoSnippet.ts b/tests/cases/fourslash/jsxAttributeCompletionStyleNoSnippet.ts new file mode 100644 index 0000000000000..8c555f720340c --- /dev/null +++ b/tests/cases/fourslash/jsxAttributeCompletionStyleNoSnippet.ts @@ -0,0 +1,75 @@ +/// + +// @Filename: foo.tsx +//// declare namespace JSX { +//// interface Element { } +//// interface IntrinsicElements { +//// foo: { +//// prop_a: boolean; +//// prop_b: string; +//// prop_c: any; +//// prop_d: { p1: string; } +//// prop_e: string | undefined; +//// prop_f: boolean | undefined | { p1: string; }; +//// prop_g: { p1: string; } | undefined; +//// prop_h?: string; +//// prop_i?: boolean; +//// prop_j?: { p1: string; }; +//// } +//// } +//// } +//// +//// + +verify.completions({ + marker: "", + exact: [ + { + name: "prop_a", + isSnippet: undefined, + }, + { + name: "prop_b", + isSnippet: undefined, + }, + { + name: "prop_c", + isSnippet: undefined, + }, + { + name: "prop_d", + isSnippet: undefined, + }, + { + name: "prop_e", + isSnippet: undefined, + }, + { + name: "prop_f", + isSnippet: undefined, + }, + { + name: "prop_g", + isSnippet: undefined, + }, + { + name: "prop_h", + isSnippet: undefined, + sortText: completion.SortText.OptionalMember, + }, + { + name: "prop_i", + isSnippet: undefined, + sortText: completion.SortText.OptionalMember, + }, + { + name: "prop_j", + isSnippet: undefined, + sortText: completion.SortText.OptionalMember, + } + ], + preferences: { + jsxAttributeCompletionStyle: "auto", + includeCompletionsWithSnippetText: false + } +}); \ No newline at end of file diff --git a/tests/cases/fourslash/jsxAttributeCompletionStyleNone.ts b/tests/cases/fourslash/jsxAttributeCompletionStyleNone.ts index 67e52cfeb30e9..99d0a7ab31cf0 100644 --- a/tests/cases/fourslash/jsxAttributeCompletionStyleNone.ts +++ b/tests/cases/fourslash/jsxAttributeCompletionStyleNone.ts @@ -10,7 +10,7 @@ //// prop_c: any; //// prop_d: { p1: string; } //// prop_e: string | undefined; -//// prop_f: boolean | undefined; +//// prop_f: boolean | undefined | { p1: string; }; //// prop_g: { p1: string; } | undefined; //// prop_h?: string; //// prop_i?: boolean; @@ -69,6 +69,7 @@ verify.completions({ } ], preferences: { - jsxAttributeCompletionStyle: "none" + jsxAttributeCompletionStyle: "none", + includeCompletionsWithSnippetText: true } }); \ No newline at end of file From fbf840114ef38aa044c6b4840d5a43747bcf8a4e Mon Sep 17 00:00:00 2001 From: Armando Aguirre Date: Mon, 20 Sep 2021 16:23:10 -0700 Subject: [PATCH 5/5] Escaped snippet --- src/services/completions.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index 9724457796227..c5ddaeca0bfdf 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -675,7 +675,7 @@ namespace ts.Completions { hasAction = !importCompletionNode; } - const kind = SymbolDisplay.getSymbolKind(typeChecker, symbol, location); // TODO: GH#18217 + const kind = SymbolDisplay.getSymbolKind(typeChecker, symbol, location); if (kind === ScriptElementKind.jsxAttribute && preferences.includeCompletionsWithSnippetText && preferences.jsxAttributeCompletionStyle && preferences.jsxAttributeCompletionStyle !== "none") { let useBraces = preferences.jsxAttributeCompletionStyle === "braces"; const type = typeChecker.getTypeOfSymbolAtLocation(symbol, location); @@ -687,7 +687,7 @@ namespace ts.Completions { ) { if (type.flags & TypeFlags.StringLike || (type.flags & TypeFlags.Union && every((type as UnionType).types, type => !!(type.flags & (TypeFlags.StringLike | TypeFlags.Undefined))))) { // If is string like or undefined use quotes - insertText = `${name}=${quote(sourceFile, preferences, "$1")}`; + insertText = `${escapeSnippetText(name)}=${quote(sourceFile, preferences, "$1")}`; isSnippet = true; } else { @@ -697,7 +697,7 @@ namespace ts.Completions { } if (useBraces) { - insertText = `${name}={$1}`; + insertText = `${escapeSnippetText(name)}={$1}`; isSnippet = true; } @@ -732,6 +732,10 @@ namespace ts.Completions { }; } + function escapeSnippetText(text: string): string { + return text.replace(/\$/gm, "\\$"); + } + function originToCompletionEntryData(origin: SymbolOriginInfoExport): CompletionEntryData | undefined { return { exportName: origin.exportName, @@ -754,10 +758,10 @@ namespace ts.Completions { const importKind = codefix.getImportKind(sourceFile, exportKind, options, /*forceImportKeyword*/ true); const suffix = useSemicolons ? ";" : ""; switch (importKind) { - case ImportKind.CommonJS: return { replacementSpan, insertText: `import ${name}${tabStop} = require(${quotedModuleSpecifier})${suffix}` }; - case ImportKind.Default: return { replacementSpan, insertText: `import ${name}${tabStop} from ${quotedModuleSpecifier}${suffix}` }; - case ImportKind.Namespace: return { replacementSpan, insertText: `import * as ${name} from ${quotedModuleSpecifier}${suffix}` }; - case ImportKind.Named: return { replacementSpan, insertText: `import { ${name}${tabStop} } from ${quotedModuleSpecifier}${suffix}` }; + case ImportKind.CommonJS: return { replacementSpan, insertText: `import ${escapeSnippetText(name)}${tabStop} = require(${quotedModuleSpecifier})${suffix}` }; + case ImportKind.Default: return { replacementSpan, insertText: `import ${escapeSnippetText(name)}${tabStop} from ${quotedModuleSpecifier}${suffix}` }; + case ImportKind.Namespace: return { replacementSpan, insertText: `import * as ${escapeSnippetText(name)} from ${quotedModuleSpecifier}${suffix}` }; + case ImportKind.Named: return { replacementSpan, insertText: `import { ${escapeSnippetText(name)}${tabStop} } from ${quotedModuleSpecifier}${suffix}` }; } }