Skip to content

Commit dcd3b5e

Browse files
author
Andy
authored
At <div x=/**/, completion insertText should be wrapped in braces (#21372)
1 parent b9bb745 commit dcd3b5e

File tree

2 files changed

+56
-16
lines changed

2 files changed

+56
-16
lines changed

Diff for: src/services/completions.ts

+33-16
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ namespace ts.Completions {
7878
}
7979

8080
function completionInfoFromData(sourceFile: SourceFile, typeChecker: TypeChecker, compilerOptions: CompilerOptions, log: Log, completionData: CompletionData, includeInsertTextCompletions: boolean): CompletionInfo {
81-
const { symbols, completionKind, isNewIdentifierLocation, location, propertyAccessToConvert, keywordFilters, symbolToOriginInfoMap, recommendedCompletion } = completionData;
81+
const { symbols, completionKind, isNewIdentifierLocation, location, propertyAccessToConvert, keywordFilters, symbolToOriginInfoMap, recommendedCompletion, isJsxInitializer } = completionData;
8282

8383
if (sourceFile.languageVariant === LanguageVariant.JSX && location && location.parent && isJsxClosingElement(location.parent)) {
8484
// In the TypeScript JSX element, if such element is not defined. When users query for completion at closing tag,
@@ -99,15 +99,15 @@ namespace ts.Completions {
9999
const entries: CompletionEntry[] = [];
100100

101101
if (isSourceFileJavaScript(sourceFile)) {
102-
const uniqueNames = getCompletionEntriesFromSymbols(symbols, entries, location, sourceFile, typeChecker, compilerOptions.target, log, completionKind, includeInsertTextCompletions, propertyAccessToConvert, recommendedCompletion, symbolToOriginInfoMap);
102+
const uniqueNames = getCompletionEntriesFromSymbols(symbols, entries, location, sourceFile, typeChecker, compilerOptions.target, log, completionKind, includeInsertTextCompletions, propertyAccessToConvert, isJsxInitializer, recommendedCompletion, symbolToOriginInfoMap);
103103
getJavaScriptCompletionEntries(sourceFile, location.pos, uniqueNames, compilerOptions.target, entries);
104104
}
105105
else {
106106
if ((!symbols || symbols.length === 0) && keywordFilters === KeywordCompletionFilters.None) {
107107
return undefined;
108108
}
109109

110-
getCompletionEntriesFromSymbols(symbols, entries, location, sourceFile, typeChecker, compilerOptions.target, log, completionKind, includeInsertTextCompletions, propertyAccessToConvert, recommendedCompletion, symbolToOriginInfoMap);
110+
getCompletionEntriesFromSymbols(symbols, entries, location, sourceFile, typeChecker, compilerOptions.target, log, completionKind, includeInsertTextCompletions, propertyAccessToConvert, isJsxInitializer, recommendedCompletion, symbolToOriginInfoMap);
111111
}
112112

113113
// TODO add filter for keyword based on type/value/namespace and also location
@@ -167,26 +167,35 @@ namespace ts.Completions {
167167
origin: SymbolOriginInfo | undefined,
168168
recommendedCompletion: Symbol | undefined,
169169
propertyAccessToConvert: PropertyAccessExpression | undefined,
170+
isJsxInitializer: boolean,
170171
includeInsertTextCompletions: boolean,
171172
): CompletionEntry | undefined {
172173
const info = getCompletionEntryDisplayNameForSymbol(symbol, target, origin, kind);
173174
if (!info) {
174175
return undefined;
175176
}
176177
const { name, needsConvertPropertyAccess } = info;
177-
if (needsConvertPropertyAccess && !includeInsertTextCompletions) {
178-
return undefined;
179-
}
180178

181179
let insertText: string | undefined;
182180
let replacementSpan: TextSpan | undefined;
183-
if (origin && origin.type === "this-type") {
184-
insertText = needsConvertPropertyAccess ? `this["${name}"]` : `this.${name}`;
181+
if (includeInsertTextCompletions) {
182+
if (origin && origin.type === "this-type") {
183+
insertText = needsConvertPropertyAccess ? `this["${name}"]` : `this.${name}`;
184+
}
185+
else if (needsConvertPropertyAccess) {
186+
// TODO: GH#20619 Use configured quote style
187+
insertText = `["${name}"]`;
188+
replacementSpan = createTextSpanFromBounds(findChildOfKind(propertyAccessToConvert!, SyntaxKind.DotToken, sourceFile)!.getStart(sourceFile), propertyAccessToConvert!.name.end);
189+
}
190+
191+
if (isJsxInitializer) {
192+
if (insertText === undefined) insertText = name;
193+
insertText = `{${insertText}}`;
194+
}
185195
}
186-
else if (needsConvertPropertyAccess) {
187-
// TODO: GH#20619 Use configured quote style
188-
insertText = `["${name}"]`;
189-
replacementSpan = createTextSpanFromBounds(findChildOfKind(propertyAccessToConvert!, SyntaxKind.DotToken, sourceFile)!.getStart(sourceFile), propertyAccessToConvert!.name.end);
196+
197+
if (insertText !== undefined && !includeInsertTextCompletions) {
198+
return undefined;
190199
}
191200

192201
// TODO(drosen): Right now we just permit *all* semantic meanings when calling
@@ -235,6 +244,7 @@ namespace ts.Completions {
235244
kind: CompletionKind,
236245
includeInsertTextCompletions?: boolean,
237246
propertyAccessToConvert?: PropertyAccessExpression | undefined,
247+
isJsxInitializer?: boolean,
238248
recommendedCompletion?: Symbol,
239249
symbolToOriginInfoMap?: SymbolOriginInfoMap,
240250
): Map<true> {
@@ -246,7 +256,7 @@ namespace ts.Completions {
246256
const uniques = createMap<true>();
247257
for (const symbol of symbols) {
248258
const origin = symbolToOriginInfoMap ? symbolToOriginInfoMap[getSymbolId(symbol)] : undefined;
249-
const entry = createCompletionEntry(symbol, location, sourceFile, typeChecker, target, kind, origin, recommendedCompletion, propertyAccessToConvert, includeInsertTextCompletions);
259+
const entry = createCompletionEntry(symbol, location, sourceFile, typeChecker, target, kind, origin, recommendedCompletion, propertyAccessToConvert, isJsxInitializer, includeInsertTextCompletions);
250260
if (!entry) {
251261
continue;
252262
}
@@ -483,6 +493,7 @@ namespace ts.Completions {
483493
location: Node;
484494
symbolToOriginInfoMap: SymbolOriginInfoMap;
485495
previousToken: Node;
496+
readonly isJsxInitializer: boolean;
486497
}
487498
function getSymbolCompletionFromEntryId(
488499
typeChecker: TypeChecker,
@@ -501,7 +512,7 @@ namespace ts.Completions {
501512
return { type: "request", request: completionData };
502513
}
503514

504-
const { symbols, location, completionKind, symbolToOriginInfoMap, previousToken } = completionData;
515+
const { symbols, location, completionKind, symbolToOriginInfoMap, previousToken, isJsxInitializer } = completionData;
505516

506517
// Find the symbol with the matching entry name.
507518
// We don't need to perform character checks here because we're only comparing the
@@ -510,7 +521,7 @@ namespace ts.Completions {
510521
return firstDefined<Symbol, SymbolCompletion>(symbols, (symbol): SymbolCompletion => { // TODO: Shouldn't need return type annotation (GH#12632)
511522
const origin = symbolToOriginInfoMap[getSymbolId(symbol)];
512523
const info = getCompletionEntryDisplayNameForSymbol(symbol, compilerOptions.target, origin, completionKind);
513-
return info && info.name === name && getSourceFromOrigin(origin) === source ? { type: "symbol" as "symbol", symbol, location, symbolToOriginInfoMap, previousToken } : undefined;
524+
return info && info.name === name && getSourceFromOrigin(origin) === source ? { type: "symbol" as "symbol", symbol, location, symbolToOriginInfoMap, previousToken, isJsxInitializer } : undefined;
514525
}) || { type: "none" };
515526
}
516527

@@ -678,6 +689,7 @@ namespace ts.Completions {
678689
readonly symbolToOriginInfoMap: SymbolOriginInfoMap;
679690
readonly recommendedCompletion: Symbol | undefined;
680691
readonly previousToken: Node | undefined;
692+
readonly isJsxInitializer: boolean;
681693
}
682694
type Request = { readonly kind: CompletionDataKind.JsDocTagName | CompletionDataKind.JsDocTag } | { readonly kind: CompletionDataKind.JsDocParameterName, tag: JSDocParameterTag };
683695

@@ -859,6 +871,7 @@ namespace ts.Completions {
859871
let isRightOfDot = false;
860872
let isRightOfOpenTag = false;
861873
let isStartingCloseTag = false;
874+
let isJsxInitializer = false;
862875

863876
let location = getTouchingPropertyName(sourceFile, position, insideJsDocTagTypeExpression); // TODO: GH#15853
864877
if (contextToken) {
@@ -917,6 +930,10 @@ namespace ts.Completions {
917930
location = contextToken;
918931
}
919932
break;
933+
934+
case SyntaxKind.JsxAttribute:
935+
isJsxInitializer = previousToken.kind === SyntaxKind.EqualsToken;
936+
break;
920937
}
921938
}
922939
}
@@ -962,7 +979,7 @@ namespace ts.Completions {
962979
log("getCompletionData: Semantic work: " + (timestamp() - semanticStart));
963980

964981
const recommendedCompletion = previousToken && getRecommendedCompletion(previousToken, typeChecker);
965-
return { kind: CompletionDataKind.Data, symbols, completionKind, propertyAccessToConvert, isNewIdentifierLocation, location, keywordFilters, symbolToOriginInfoMap, recommendedCompletion, previousToken };
982+
return { kind: CompletionDataKind.Data, symbols, completionKind, propertyAccessToConvert, isNewIdentifierLocation, location, keywordFilters, symbolToOriginInfoMap, recommendedCompletion, previousToken, isJsxInitializer };
966983

967984
type JSDocTagWithTypeExpression = JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag;
968985

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
// @Filename: /a.tsx
4+
////function f(this: { p: number; "a b": number }, x: number): void {
5+
//// <div foo=/**/ />;
6+
////}
7+
8+
goTo.marker();
9+
10+
verify.completionListContains("x", "(parameter) x: number", "", "parameter", undefined, undefined, {
11+
includeInsertTextCompletions: true,
12+
insertText: "{x}",
13+
});
14+
15+
verify.completionListContains("p", "(property) p: number", "", "property", undefined, undefined, {
16+
includeInsertTextCompletions: true,
17+
insertText: "{this.p}",
18+
});
19+
20+
verify.completionListContains("a b", '(property) "a b": number', "", "property", undefined, undefined, {
21+
includeInsertTextCompletions: true,
22+
insertText: '{this["a b"]}',
23+
});

0 commit comments

Comments
 (0)