diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 4b9816f9fcb05..90fa7aaf92166 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -3156,6 +3156,9 @@ Actual: ${stringify(fullActual)}`); assert.equal(item.hasAction, hasAction, "hasAction"); assert.equal(item.isRecommended, options && options.isRecommended, "isRecommended"); assert.equal(item.insertText, options && options.insertText, "insertText"); + if (options && options.replacementSpan) { // TODO: GH#21679 + assert.deepEqual(item.replacementSpan, options && options.replacementSpan && textSpanFromRange(options.replacementSpan), "replacementSpan"); + } } private findFile(indexOrName: string | number) { @@ -4616,6 +4619,7 @@ namespace FourSlashInterface { sourceDisplay: string; isRecommended?: true; insertText?: string; + replacementSpan?: FourSlash.Range; } export interface VerifyDocumentHighlightsOptions { diff --git a/src/services/completions.ts b/src/services/completions.ts index 6f4cadec7e19d..2687f3fd8b6c6 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -167,7 +167,7 @@ namespace ts.Completions { origin: SymbolOriginInfo | undefined, recommendedCompletion: Symbol | undefined, propertyAccessToConvert: PropertyAccessExpression | undefined, - isJsxInitializer: boolean, + isJsxInitializer: IsJsxInitializer, includeInsertTextCompletions: boolean, ): CompletionEntry | undefined { const info = getCompletionEntryDisplayNameForSymbol(symbol, target, origin, kind); @@ -194,6 +194,9 @@ namespace ts.Completions { if (isJsxInitializer) { if (insertText === undefined) insertText = name; insertText = `{${insertText}}`; + if (typeof isJsxInitializer !== "boolean") { + replacementSpan = createTextSpanFromNode(isJsxInitializer, sourceFile); + } } } @@ -247,7 +250,7 @@ namespace ts.Completions { kind: CompletionKind, includeInsertTextCompletions?: boolean, propertyAccessToConvert?: PropertyAccessExpression | undefined, - isJsxInitializer?: boolean, + isJsxInitializer?: IsJsxInitializer, recommendedCompletion?: Symbol, symbolToOriginInfoMap?: SymbolOriginInfoMap, ): Map { @@ -496,7 +499,7 @@ namespace ts.Completions { location: Node; symbolToOriginInfoMap: SymbolOriginInfoMap; previousToken: Node; - readonly isJsxInitializer: boolean; + readonly isJsxInitializer: IsJsxInitializer; } function getSymbolCompletionFromEntryId( typeChecker: TypeChecker, @@ -680,6 +683,8 @@ namespace ts.Completions { } const enum CompletionDataKind { Data, JsDocTagName, JsDocTag, JsDocParameterName } + /** true: after the `=` sign but no identifier has been typed yet. Else is the Identifier after the initializer. */ + type IsJsxInitializer = boolean | Identifier; interface CompletionData { readonly kind: CompletionDataKind.Data; readonly symbols: ReadonlyArray; @@ -692,7 +697,7 @@ namespace ts.Completions { readonly symbolToOriginInfoMap: SymbolOriginInfoMap; readonly recommendedCompletion: Symbol | undefined; readonly previousToken: Node | undefined; - readonly isJsxInitializer: boolean; + readonly isJsxInitializer: IsJsxInitializer; } type Request = { readonly kind: CompletionDataKind.JsDocTagName | CompletionDataKind.JsDocTag } | { readonly kind: CompletionDataKind.JsDocParameterName, tag: JSDocParameterTag }; @@ -874,7 +879,7 @@ namespace ts.Completions { let isRightOfDot = false; let isRightOfOpenTag = false; let isStartingCloseTag = false; - let isJsxInitializer = false; + let isJsxInitializer: IsJsxInitializer = false; let location = getTouchingPropertyName(sourceFile, position, insideJsDocTagTypeExpression); // TODO: GH#15853 if (contextToken) { @@ -935,7 +940,15 @@ namespace ts.Completions { break; case SyntaxKind.JsxAttribute: - isJsxInitializer = previousToken.kind === SyntaxKind.EqualsToken; + switch (previousToken.kind) { + case SyntaxKind.EqualsToken: + isJsxInitializer = true; + break; + case SyntaxKind.Identifier: + if (previousToken !== (parent as JsxAttribute).name) { + isJsxInitializer = previousToken as Identifier; + } + } break; } } diff --git a/tests/cases/fourslash/completionsJsxAttributeInitializer2.ts b/tests/cases/fourslash/completionsJsxAttributeInitializer2.ts new file mode 100644 index 0000000000000..c295424175550 --- /dev/null +++ b/tests/cases/fourslash/completionsJsxAttributeInitializer2.ts @@ -0,0 +1,13 @@ +/// + +// @Filename: /a.tsx +////const foo = 0; +////
; + +const [replacementSpan] = test.ranges(); +goTo.marker(); +verify.completionListContains("foo", "const foo: 0", undefined, "const", undefined, undefined, { + includeInsertTextCompletions: true, + insertText: "{foo}", + replacementSpan, +}); diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 1d7cb0810f4de..3079736ea1174 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -157,6 +157,7 @@ declare namespace FourSlashInterface { sourceDisplay?: string, isRecommended?: true, insertText?: string, + replacementSpan?: Range, }, ): void; completionListItemsCountIsGreaterThan(count: number): void;