Skip to content

Commit c46ab32

Browse files
author
Andy
authored
Support incomplete identifier in JSX initializer completions (#21681) (#21692)
1 parent 07c9505 commit c46ab32

File tree

4 files changed

+37
-6
lines changed

4 files changed

+37
-6
lines changed

src/harness/fourslash.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3166,6 +3166,9 @@ Actual: ${stringify(fullActual)}`);
31663166
assert.equal(item.hasAction, hasAction, "hasAction");
31673167
assert.equal(item.isRecommended, options && options.isRecommended, "isRecommended");
31683168
assert.equal(item.insertText, options && options.insertText, "insertText");
3169+
if (options && options.replacementSpan) { // TODO: GH#21679
3170+
assert.deepEqual(item.replacementSpan, options && options.replacementSpan && textSpanFromRange(options.replacementSpan), "replacementSpan");
3171+
}
31693172
}
31703173

31713174
private findFile(indexOrName: string | number) {
@@ -4622,6 +4625,7 @@ namespace FourSlashInterface {
46224625
sourceDisplay: string;
46234626
isRecommended?: true;
46244627
insertText?: string;
4628+
replacementSpan?: FourSlash.Range;
46254629
}
46264630

46274631
export interface NewContentOptions {

src/services/completions.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ namespace ts.Completions {
167167
origin: SymbolOriginInfo | undefined,
168168
recommendedCompletion: Symbol | undefined,
169169
propertyAccessToConvert: PropertyAccessExpression | undefined,
170-
isJsxInitializer: boolean,
170+
isJsxInitializer: IsJsxInitializer,
171171
includeInsertTextCompletions: boolean,
172172
): CompletionEntry | undefined {
173173
const info = getCompletionEntryDisplayNameForSymbol(symbol, target, origin, kind);
@@ -193,6 +193,9 @@ namespace ts.Completions {
193193
if (isJsxInitializer) {
194194
if (insertText === undefined) insertText = name;
195195
insertText = `{${insertText}}`;
196+
if (typeof isJsxInitializer !== "boolean") {
197+
replacementSpan = createTextSpanFromNode(isJsxInitializer, sourceFile);
198+
}
196199
}
197200
}
198201

@@ -250,7 +253,7 @@ namespace ts.Completions {
250253
kind: CompletionKind,
251254
includeInsertTextCompletions?: boolean,
252255
propertyAccessToConvert?: PropertyAccessExpression | undefined,
253-
isJsxInitializer?: boolean,
256+
isJsxInitializer?: IsJsxInitializer,
254257
recommendedCompletion?: Symbol,
255258
symbolToOriginInfoMap?: SymbolOriginInfoMap,
256259
): Map<true> {
@@ -499,7 +502,7 @@ namespace ts.Completions {
499502
location: Node;
500503
symbolToOriginInfoMap: SymbolOriginInfoMap;
501504
previousToken: Node;
502-
readonly isJsxInitializer: boolean;
505+
readonly isJsxInitializer: IsJsxInitializer;
503506
}
504507
function getSymbolCompletionFromEntryId(
505508
typeChecker: TypeChecker,
@@ -683,6 +686,8 @@ namespace ts.Completions {
683686
}
684687

685688
const enum CompletionDataKind { Data, JsDocTagName, JsDocTag, JsDocParameterName }
689+
/** true: after the `=` sign but no identifier has been typed yet. Else is the Identifier after the initializer. */
690+
type IsJsxInitializer = boolean | Identifier;
686691
interface CompletionData {
687692
readonly kind: CompletionDataKind.Data;
688693
readonly symbols: ReadonlyArray<Symbol>;
@@ -695,7 +700,7 @@ namespace ts.Completions {
695700
readonly symbolToOriginInfoMap: SymbolOriginInfoMap;
696701
readonly recommendedCompletion: Symbol | undefined;
697702
readonly previousToken: Node | undefined;
698-
readonly isJsxInitializer: boolean;
703+
readonly isJsxInitializer: IsJsxInitializer;
699704
}
700705
type Request = { readonly kind: CompletionDataKind.JsDocTagName | CompletionDataKind.JsDocTag } | { readonly kind: CompletionDataKind.JsDocParameterName, tag: JSDocParameterTag };
701706

@@ -877,7 +882,7 @@ namespace ts.Completions {
877882
let isRightOfDot = false;
878883
let isRightOfOpenTag = false;
879884
let isStartingCloseTag = false;
880-
let isJsxInitializer = false;
885+
let isJsxInitializer: IsJsxInitializer = false;
881886

882887
let location = getTouchingPropertyName(sourceFile, position, insideJsDocTagTypeExpression); // TODO: GH#15853
883888
if (contextToken) {
@@ -938,7 +943,15 @@ namespace ts.Completions {
938943
break;
939944

940945
case SyntaxKind.JsxAttribute:
941-
isJsxInitializer = previousToken.kind === SyntaxKind.EqualsToken;
946+
switch (previousToken.kind) {
947+
case SyntaxKind.EqualsToken:
948+
isJsxInitializer = true;
949+
break;
950+
case SyntaxKind.Identifier:
951+
if (previousToken !== (parent as JsxAttribute).name) {
952+
isJsxInitializer = previousToken as Identifier;
953+
}
954+
}
942955
break;
943956
}
944957
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
// @Filename: /a.tsx
4+
////const foo = 0;
5+
////<div x=[|f/**/|] />;
6+
7+
const [replacementSpan] = test.ranges();
8+
goTo.marker();
9+
verify.completionListContains("foo", "const foo: 0", undefined, "const", undefined, undefined, {
10+
includeInsertTextCompletions: true,
11+
insertText: "{foo}",
12+
replacementSpan,
13+
});

tests/cases/fourslash/fourslash.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ declare namespace FourSlashInterface {
157157
sourceDisplay?: string,
158158
isRecommended?: true,
159159
insertText?: string,
160+
replacementSpan?: Range,
160161
},
161162
): void;
162163
completionListItemsCountIsGreaterThan(count: number): void;

0 commit comments

Comments
 (0)