mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-11 06:02:53 -05:00
Added Jsx Snippet Completion feature (#45903)
* Added Jsx completion feature and tests * Renamed jsxSnippetCompletion to jsxAttributeCompletionStyle * Renamed tests files * Changed boolean filter * Escaped snippet
This commit is contained in:
@@ -8575,6 +8575,7 @@ namespace ts {
|
||||
readonly providePrefixAndSuffixTextForRename?: boolean;
|
||||
readonly includePackageJsonAutoImports?: "auto" | "on" | "off";
|
||||
readonly provideRefactorNotApplicableReason?: boolean;
|
||||
readonly jsxAttributeCompletionStyle?: "auto" | "braces" | "none";
|
||||
}
|
||||
|
||||
/** Represents a bigint literal value without requiring bigint support */
|
||||
|
||||
@@ -3391,6 +3391,7 @@ namespace ts.server.protocol {
|
||||
readonly provideRefactorNotApplicableReason?: boolean;
|
||||
readonly allowRenameOfImportPath?: boolean;
|
||||
readonly includePackageJsonAutoImports?: "auto" | "on" | "off";
|
||||
readonly jsxAttributeCompletionStyle?: "auto" | "braces" | "none";
|
||||
|
||||
readonly displayPartsForJSDoc?: boolean;
|
||||
readonly generateReturnInDocTemplate?: boolean;
|
||||
|
||||
@@ -675,6 +675,37 @@ namespace ts.Completions {
|
||||
hasAction = !importCompletionNode;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// 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 && 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
|
||||
insertText = `${escapeSnippetText(name)}=${quote(sourceFile, preferences, "$1")}`;
|
||||
isSnippet = true;
|
||||
}
|
||||
else {
|
||||
// Use braces for everything else
|
||||
useBraces = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (useBraces) {
|
||||
insertText = `${escapeSnippetText(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),
|
||||
@@ -701,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,
|
||||
@@ -723,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}` };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4019,6 +4019,7 @@ declare namespace ts {
|
||||
readonly providePrefixAndSuffixTextForRename?: boolean;
|
||||
readonly includePackageJsonAutoImports?: "auto" | "on" | "off";
|
||||
readonly provideRefactorNotApplicableReason?: boolean;
|
||||
readonly jsxAttributeCompletionStyle?: "auto" | "braces" | "none";
|
||||
}
|
||||
/** Represents a bigint literal value without requiring bigint support */
|
||||
export interface PseudoBigInt {
|
||||
@@ -9485,6 +9486,7 @@ declare namespace ts.server.protocol {
|
||||
readonly provideRefactorNotApplicableReason?: boolean;
|
||||
readonly allowRenameOfImportPath?: boolean;
|
||||
readonly includePackageJsonAutoImports?: "auto" | "on" | "off";
|
||||
readonly jsxAttributeCompletionStyle?: "auto" | "braces" | "none";
|
||||
readonly displayPartsForJSDoc?: boolean;
|
||||
readonly generateReturnInDocTemplate?: boolean;
|
||||
}
|
||||
|
||||
@@ -4019,6 +4019,7 @@ declare namespace ts {
|
||||
readonly providePrefixAndSuffixTextForRename?: boolean;
|
||||
readonly includePackageJsonAutoImports?: "auto" | "on" | "off";
|
||||
readonly provideRefactorNotApplicableReason?: boolean;
|
||||
readonly jsxAttributeCompletionStyle?: "auto" | "braces" | "none";
|
||||
}
|
||||
/** Represents a bigint literal value without requiring bigint support */
|
||||
export interface PseudoBigInt {
|
||||
|
||||
@@ -652,6 +652,7 @@ declare namespace FourSlashInterface {
|
||||
readonly includeAutomaticOptionalChainCompletions?: boolean;
|
||||
readonly importModuleSpecifierPreference?: "shortest" | "project-relative" | "relative" | "non-relative";
|
||||
readonly importModuleSpecifierEnding?: "minimal" | "index" | "js";
|
||||
readonly jsxAttributeCompletionStyle?: "auto" | "braces" | "none";
|
||||
}
|
||||
interface InlayHintsOptions extends UserPreferences {
|
||||
readonly includeInlayParameterNameHints?: "none" | "literals" | "all";
|
||||
|
||||
89
tests/cases/fourslash/jsxAttributeCompletionStyleAuto.ts
Normal file
89
tests/cases/fourslash/jsxAttributeCompletionStyleAuto.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @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; };
|
||||
//// }
|
||||
//// }
|
||||
//// }
|
||||
////
|
||||
//// <foo [|prop_/**/|] />
|
||||
|
||||
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: {
|
||||
jsxAttributeCompletionStyle: "auto",
|
||||
includeCompletionsWithSnippetText: true
|
||||
}
|
||||
});
|
||||
95
tests/cases/fourslash/jsxAttributeCompletionStyleBraces.ts
Normal file
95
tests/cases/fourslash/jsxAttributeCompletionStyleBraces.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @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; };
|
||||
//// }
|
||||
//// }
|
||||
//// }
|
||||
////
|
||||
//// <foo [|prop_/**/|] />
|
||||
|
||||
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: {
|
||||
jsxAttributeCompletionStyle: "braces",
|
||||
includeCompletionsWithSnippetText: true
|
||||
}
|
||||
});
|
||||
75
tests/cases/fourslash/jsxAttributeCompletionStyleDefault.ts
Normal file
75
tests/cases/fourslash/jsxAttributeCompletionStyleDefault.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @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; };
|
||||
//// }
|
||||
//// }
|
||||
//// }
|
||||
////
|
||||
//// <foo [|prop_/**/|] />
|
||||
|
||||
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: undefined,
|
||||
includeCompletionsWithSnippetText: true
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,75 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @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; };
|
||||
//// }
|
||||
//// }
|
||||
//// }
|
||||
////
|
||||
//// <foo [|prop_/**/|] />
|
||||
|
||||
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
|
||||
}
|
||||
});
|
||||
75
tests/cases/fourslash/jsxAttributeCompletionStyleNone.ts
Normal file
75
tests/cases/fourslash/jsxAttributeCompletionStyleNone.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @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; };
|
||||
//// }
|
||||
//// }
|
||||
//// }
|
||||
////
|
||||
//// <foo [|prop_/**/|] />
|
||||
|
||||
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: "none",
|
||||
includeCompletionsWithSnippetText: true
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user