Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions src/services/completions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,7 @@ namespace ts.Completions {
isJsxInitializer,
isTypeOnlyLocation,
isJsxIdentifierExpected,
isRightOfOpenTag,
importCompletionNode,
insideJsDocTagTypeExpression,
symbolToSortTextIdMap,
Expand Down Expand Up @@ -495,7 +496,9 @@ namespace ts.Completions {
importCompletionNode,
recommendedCompletion,
symbolToOriginInfoMap,
symbolToSortTextIdMap
symbolToSortTextIdMap,
isJsxIdentifierExpected,
isRightOfOpenTag,
);
getJSCompletionEntries(sourceFile, location.pos, uniqueNames, getEmitScriptTarget(compilerOptions), entries); // TODO: GH#18217
}
Expand Down Expand Up @@ -526,7 +529,9 @@ namespace ts.Completions {
importCompletionNode,
recommendedCompletion,
symbolToOriginInfoMap,
symbolToSortTextIdMap
symbolToSortTextIdMap,
isJsxIdentifierExpected,
isRightOfOpenTag,
);
}

Expand Down Expand Up @@ -669,6 +674,8 @@ namespace ts.Completions {
preferences: UserPreferences,
completionKind: CompletionKind,
formatContext: formatting.FormatContext | undefined,
isJsxIdentifierExpected: boolean | undefined,
isRightOfOpenTag: boolean | undefined,
): CompletionEntry | undefined {
let insertText: string | undefined;
let replacementSpan = getReplacementSpanForContextToken(replacementToken);
Expand Down Expand Up @@ -744,8 +751,7 @@ namespace ts.Completions {
}
}

const kind = SymbolDisplay.getSymbolKind(typeChecker, symbol, location);
if (kind === ScriptElementKind.jsxAttribute && preferences.includeCompletionsWithSnippetText && preferences.jsxAttributeCompletionStyle && preferences.jsxAttributeCompletionStyle !== "none") {
if (isJsxIdentifierExpected && !isRightOfOpenTag && preferences.includeCompletionsWithSnippetText && preferences.jsxAttributeCompletionStyle && preferences.jsxAttributeCompletionStyle !== "none") {
let useBraces = preferences.jsxAttributeCompletionStyle === "braces";
const type = typeChecker.getTypeOfSymbolAtLocation(symbol, location);

Expand Down Expand Up @@ -790,7 +796,7 @@ namespace ts.Completions {
// entries (like JavaScript identifier entries).
return {
name,
kind,
kind: SymbolDisplay.getSymbolKind(typeChecker, symbol, location),
kindModifiers: SymbolDisplay.getSymbolModifiers(typeChecker, symbol),
sortText,
source,
Expand Down Expand Up @@ -1186,6 +1192,8 @@ namespace ts.Completions {
recommendedCompletion?: Symbol,
symbolToOriginInfoMap?: SymbolOriginInfoMap,
symbolToSortTextIdMap?: SymbolSortTextIdMap,
isJsxIdentifierExpected?: boolean,
isRightOfOpenTag?: boolean,
): UniqueNameSet {
const start = timestamp();
const variableDeclaration = getVariableDeclaration(location);
Expand Down Expand Up @@ -1228,6 +1236,8 @@ namespace ts.Completions {
preferences,
kind,
formatContext,
isJsxIdentifierExpected,
isRightOfOpenTag,
);
if (!entry) {
continue;
Expand Down Expand Up @@ -1580,6 +1590,7 @@ namespace ts.Completions {
readonly isTypeOnlyLocation: boolean;
/** In JSX tag name and attribute names, identifiers like "my-tag" or "aria-name" is valid identifier. */
readonly isJsxIdentifierExpected: boolean;
readonly isRightOfOpenTag: boolean;
readonly importCompletionNode?: Node;
readonly hasUnresolvedAutoImports?: boolean;
}
Expand Down Expand Up @@ -1987,6 +1998,7 @@ namespace ts.Completions {
symbolToSortTextIdMap,
isTypeOnlyLocation,
isJsxIdentifierExpected,
isRightOfOpenTag,
importCompletionNode,
hasUnresolvedAutoImports,
};
Expand Down
74 changes: 74 additions & 0 deletions tests/cases/fourslash/jsxAttributeSnippetCompletionClosed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/// <reference path="fourslash.ts" />
//@Filename: file.tsx
////interface NestedInterface {
//// Foo: NestedInterface;
//// (props: {className?: string}): any;
////}
////
////declare const Foo: NestedInterface;
////
////function fn1() {
//// return <Foo>
//// <Foo /*1*/ />
//// </Foo>
////}
////function fn2() {
//// return <Foo>
//// <Foo.Foo /*2*/ />
//// </Foo>
////}
////function fn3() {
//// return <Foo>
//// <Foo.Foo cla/*3*/ />
//// </Foo>
////}
////function fn4() {
//// return <Foo>
//// <Foo.Foo cla/*4*/ something />
//// </Foo>
////}
////function fn5() {
//// return <Foo>
//// <Foo.Foo something /*5*/ />
//// </Foo>
////}
////function fn6() {
//// return <Foo>
//// <Foo.Foo something cla/*6*/ />
//// </Foo>
////}
////function fn7() {
//// return <Foo /*7*/ />
////}
////function fn8() {
//// return <Foo cla/*8*/ />
////}
////function fn9() {
//// return <Foo cla/*9*/ something />
////}
////function fn10() {
//// return <Foo something /*10*/ />
////}
////function fn11() {
//// return <Foo something cla/*11*/ />
////}

var preferences: FourSlashInterface.UserPreferences = {
jsxAttributeCompletionStyle: "braces",
includeCompletionsWithSnippetText: true,
includeCompletionsWithInsertText: true,
};

verify.completions(
{ marker: "1", preferences, includes: { name: "className", insertText: "className={$1}", text: "(JSX attribute) className?: string", isSnippet: true, sortText: completion.SortText.OptionalMember } },
{ marker: "2", preferences, includes: { name: "className", insertText: "className={$1}", text: "(JSX attribute) className?: string", isSnippet: true, sortText: completion.SortText.OptionalMember } },
{ marker: "3", preferences, includes: { name: "className", insertText: "className={$1}", text: "(JSX attribute) className?: string", isSnippet: true, sortText: completion.SortText.OptionalMember } },
{ marker: "4", preferences, includes: { name: "className", insertText: "className={$1}", text: "(JSX attribute) className?: string", isSnippet: true, sortText: completion.SortText.OptionalMember } },
{ marker: "5", preferences, includes: { name: "className", insertText: "className={$1}", text: "(JSX attribute) className?: string", isSnippet: true, sortText: completion.SortText.OptionalMember } },
{ marker: "6", preferences, includes: { name: "className", insertText: "className={$1}", text: "(JSX attribute) className?: string", isSnippet: true, sortText: completion.SortText.OptionalMember } },
{ marker: "7", preferences, includes: { name: "className", insertText: "className={$1}", text: "(JSX attribute) className?: string", isSnippet: true, sortText: completion.SortText.OptionalMember } },
{ marker: "8", preferences, includes: { name: "className", insertText: "className={$1}", text: "(JSX attribute) className?: string", isSnippet: true, sortText: completion.SortText.OptionalMember } },
{ marker: "9", preferences, includes: { name: "className", insertText: "className={$1}", text: "(JSX attribute) className?: string", isSnippet: true, sortText: completion.SortText.OptionalMember } },
{ marker: "10", preferences, includes: { name: "className", insertText: "className={$1}", text: "(JSX attribute) className?: string", isSnippet: true, sortText: completion.SortText.OptionalMember } },
{ marker: "11", preferences, includes: { name: "className", insertText: "className={$1}", text: "(JSX attribute) className?: string", isSnippet: true, sortText: completion.SortText.OptionalMember } },
)
74 changes: 74 additions & 0 deletions tests/cases/fourslash/jsxAttributeSnippetCompletionUnclosed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/// <reference path="fourslash.ts" />
//@Filename: file.tsx
////interface NestedInterface {
//// Foo: NestedInterface;
//// (props: {className?: string}): any;
////}
////
////declare const Foo: NestedInterface;
////
////function fn1() {
//// return <Foo>
//// <Foo /*1*/
//// </Foo>
////}
////function fn2() {
//// return <Foo>
//// <Foo.Foo /*2*/
//// </Foo>
////}
////function fn3() {
//// return <Foo>
//// <Foo.Foo cla/*3*/
//// </Foo>
////}
////function fn4() {
//// return <Foo>
//// <Foo.Foo cla/*4*/ something
//// </Foo>
////}
////function fn5() {
//// return <Foo>
//// <Foo.Foo something /*5*/
//// </Foo>
////}
////function fn6() {
//// return <Foo>
//// <Foo.Foo something cla/*6*/
//// </Foo>
////}
////function fn7() {
//// return <Foo /*7*/
////}
////function fn8() {
//// return <Foo cla/*8*/
////}
////function fn9() {
//// return <Foo cla/*9*/ something
////}
////function fn10() {
//// return <Foo something /*10*/
////}
////function fn11() {
//// return <Foo something cla/*11*/
////}

var preferences: FourSlashInterface.UserPreferences = {
jsxAttributeCompletionStyle: "braces",
includeCompletionsWithSnippetText: true,
includeCompletionsWithInsertText: true,
};

verify.completions(
{ marker: "1", preferences, includes: { name: "className", insertText: "className={$1}", text: "(JSX attribute) className?: string", isSnippet: true, sortText: completion.SortText.OptionalMember } },
{ marker: "2", preferences, includes: { name: "className", insertText: "className={$1}", text: "(JSX attribute) className?: string", isSnippet: true, sortText: completion.SortText.OptionalMember } },
{ marker: "3", preferences, includes: { name: "className", insertText: "className={$1}", text: "(JSX attribute) className?: string", isSnippet: true, sortText: completion.SortText.OptionalMember } },
{ marker: "4", preferences, includes: { name: "className", insertText: "className={$1}", text: "(JSX attribute) className?: string", isSnippet: true, sortText: completion.SortText.OptionalMember } },
{ marker: "5", preferences, includes: { name: "className", insertText: "className={$1}", text: "(JSX attribute) className?: string", isSnippet: true, sortText: completion.SortText.OptionalMember } },
{ marker: "6", preferences, includes: { name: "className", insertText: "className={$1}", text: "(JSX attribute) className?: string", isSnippet: true, sortText: completion.SortText.OptionalMember } },
{ marker: "7", preferences, includes: { name: "className", insertText: "className={$1}", text: "(property) className?: string", isSnippet: true, sortText: completion.SortText.OptionalMember } },
{ marker: "8", preferences, includes: { name: "className", insertText: "className={$1}", text: "(JSX attribute) className?: string", isSnippet: true, sortText: completion.SortText.OptionalMember } },
{ marker: "9", preferences, includes: { name: "className", insertText: "className={$1}", text: "(JSX attribute) className?: string", isSnippet: true, sortText: completion.SortText.OptionalMember } },
{ marker: "10", preferences, includes: { name: "className", insertText: "className={$1}", text: "(property) className?: string", isSnippet: true, sortText: completion.SortText.OptionalMember } },
{ marker: "11", preferences, includes: { name: "className", insertText: "className={$1}", text: "(JSX attribute) className?: string", isSnippet: true, sortText: completion.SortText.OptionalMember } },
)
54 changes: 54 additions & 0 deletions tests/cases/fourslash/jsxTagNameCompletionClosed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/// <reference path="fourslash.ts" />
//@Filename: file.tsx
////interface NestedInterface {
//// Foo: NestedInterface;
//// (props: {}): any;
////}
////
////declare const Foo: NestedInterface;
////
////function fn1() {
//// return <Foo>
//// </*1*/ />
//// </Foo>
////}
////function fn2() {
//// return <Foo>
//// <Fo/*2*/ />
//// </Foo>
////}
////function fn3() {
//// return <Foo>
//// <Foo./*3*/ />
//// </Foo>
////}
////function fn4() {
//// return <Foo>
//// <Foo.F/*4*/ />
//// </Foo>
////}
////function fn5() {
//// return <Foo>
//// <Foo.Foo./*5*/ />
//// </Foo>
////}
////function fn6() {
//// return <Foo>
//// <Foo.Foo.F/*6*/ />
//// </Foo>
////}

var preferences: FourSlashInterface.UserPreferences = {
jsxAttributeCompletionStyle: "braces",
includeCompletionsWithSnippetText: true,
includeCompletionsWithInsertText: true,
};

verify.completions(
{ marker: "1", preferences, includes: { name: "Foo", text: "const Foo: NestedInterface" } },
{ marker: "2", preferences, includes: { name: "Foo", text: "const Foo: NestedInterface" } },
{ marker: "3", preferences, includes: { name: "Foo", text: "(JSX attribute) NestedInterface.Foo: NestedInterface" } },
{ marker: "4", preferences, includes: { name: "Foo", text: "(property) NestedInterface.Foo: NestedInterface" } },
{ marker: "5", preferences, includes: { name: "Foo", text: "(JSX attribute) NestedInterface.Foo: NestedInterface" } },
{ marker: "6", preferences, includes: { name: "Foo", text: "(property) NestedInterface.Foo: NestedInterface" } },
)
54 changes: 54 additions & 0 deletions tests/cases/fourslash/jsxTagNameCompletionUnclosed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/// <reference path="fourslash.ts" />
//@Filename: file.tsx
////interface NestedInterface {
//// Foo: NestedInterface;
//// (props: {}): any;
////}
////
////declare const Foo: NestedInterface;
////
////function fn1() {
//// return <Foo>
//// </*1*/
//// </Foo>
////}
////function fn2() {
//// return <Foo>
//// <Fo/*2*/
//// </Foo>
////}
////function fn3() {
//// return <Foo>
//// <Foo./*3*/
//// </Foo>
////}
////function fn4() {
//// return <Foo>
//// <Foo.F/*4*/
//// </Foo>
////}
////function fn5() {
//// return <Foo>
//// <Foo.Foo./*5*/
//// </Foo>
////}
////function fn6() {
//// return <Foo>
//// <Foo.Foo.F/*6*/
//// </Foo>
////}

var preferences: FourSlashInterface.UserPreferences = {
jsxAttributeCompletionStyle: "braces",
includeCompletionsWithSnippetText: true,
includeCompletionsWithInsertText: true,
};

verify.completions(
{ marker: "1", preferences, includes: { name: "Foo", text: "const Foo: NestedInterface" } },
{ marker: "2", preferences, includes: { name: "Foo", text: "const Foo: NestedInterface" } },
{ marker: "3", preferences, includes: { name: "Foo", text: "(JSX attribute) NestedInterface.Foo: NestedInterface" } },
{ marker: "4", preferences, includes: { name: "Foo", text: "(property) NestedInterface.Foo: NestedInterface" } },
{ marker: "5", preferences, includes: { name: "Foo", text: "(JSX attribute) NestedInterface.Foo: NestedInterface" } },
{ marker: "6", preferences, includes: { name: "Foo", text: "(property) NestedInterface.Foo: NestedInterface" } },
)
35 changes: 35 additions & 0 deletions tests/cases/fourslash/jsxTagNameCompletionUnderElementClosed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/// <reference path="fourslash.ts" />
//@Filename: file.tsx
////declare namespace JSX {
//// interface IntrinsicElements {
//// button: any;
//// div: any;
//// }
////}
////function fn() {
//// return <>
//// <butto/*1*/ />
//// </>;
////}
////function fn2() {
//// return <>
//// preceding junk <butto/*2*/ />
//// </>;
////}
////function fn3() {
//// return <>
//// <butto/*3*/ style="" />
//// </>;
////}

var preferences: FourSlashInterface.UserPreferences = {
jsxAttributeCompletionStyle: "braces",
includeCompletionsWithSnippetText: true,
includeCompletionsWithInsertText: true,
};

verify.completions(
{ marker: "1", preferences, includes: { name: "button", text: "(JSX attribute) JSX.IntrinsicElements.button: any" } },
{ marker: "2", preferences, includes: { name: "button", text: "(JSX attribute) JSX.IntrinsicElements.button: any" } },
{ marker: "3", preferences, includes: { name: "button", text: "(JSX attribute) JSX.IntrinsicElements.button: any" } },
)
Loading