Skip to content

Commit c8147cb

Browse files
authored
fix(33377): use quoteStyle option for string literals completions (microsoft#36720)
1 parent d763804 commit c8147cb

10 files changed

+149
-16
lines changed

src/services/completions.ts

+24-11
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ namespace ts.Completions {
272272
}
273273

274274
for (const literal of literals) {
275-
entries.push(createCompletionEntryForLiteral(literal));
275+
entries.push(createCompletionEntryForLiteral(literal, preferences));
276276
}
277277

278278
return { isGlobalCompletion: isInSnippetScope, isMemberCompletion: isMemberCompletionKind(completionKind), isNewIdentifierLocation, entries };
@@ -317,10 +317,13 @@ namespace ts.Completions {
317317
});
318318
}
319319

320-
const completionNameForLiteral = (literal: string | number | PseudoBigInt) =>
321-
typeof literal === "object" ? pseudoBigIntToString(literal) + "n" : JSON.stringify(literal);
322-
function createCompletionEntryForLiteral(literal: string | number | PseudoBigInt): CompletionEntry {
323-
return { name: completionNameForLiteral(literal), kind: ScriptElementKind.string, kindModifiers: ScriptElementKindModifier.none, sortText: SortText.LocationPriority };
320+
function completionNameForLiteral(literal: string | number | PseudoBigInt, preferences: UserPreferences): string {
321+
return typeof literal === "object" ? pseudoBigIntToString(literal) + "n" :
322+
isString(literal) ? quote(literal, preferences) : JSON.stringify(literal);
323+
}
324+
325+
function createCompletionEntryForLiteral(literal: string | number | PseudoBigInt, preferences: UserPreferences): CompletionEntry {
326+
return { name: completionNameForLiteral(literal, preferences), kind: ScriptElementKind.string, kindModifiers: ScriptElementKindModifier.none, sortText: SortText.LocationPriority };
324327
}
325328

326329
function createCompletionEntry(
@@ -344,13 +347,13 @@ namespace ts.Completions {
344347
const useBraces = origin && originIsSymbolMember(origin) || needsConvertPropertyAccess;
345348
if (origin && originIsThisType(origin)) {
346349
insertText = needsConvertPropertyAccess
347-
? `this${insertQuestionDot ? "?." : ""}[${quote(name, preferences)}]`
350+
? `this${insertQuestionDot ? "?." : ""}[${quotePropertyName(name, preferences)}]`
348351
: `this${insertQuestionDot ? "?." : "."}${name}`;
349352
}
350353
// We should only have needsConvertPropertyAccess if there's a property access to convert. But see #21790.
351354
// Somehow there was a global with a non-identifier name. Hopefully someone will complain about getting a "foo bar" global completion and provide a repro.
352355
else if ((useBraces || insertQuestionDot) && propertyAccessToConvert) {
353-
insertText = useBraces ? needsConvertPropertyAccess ? `[${quote(name, preferences)}]` : `[${name}]` : name;
356+
insertText = useBraces ? needsConvertPropertyAccess ? `[${quotePropertyName(name, preferences)}]` : `[${name}]` : name;
354357
if (insertQuestionDot || propertyAccessToConvert.questionDotToken) {
355358
insertText = `?.${insertText}`;
356359
}
@@ -410,6 +413,14 @@ namespace ts.Completions {
410413
};
411414
}
412415

416+
function quotePropertyName(name: string, preferences: UserPreferences): string {
417+
if (/^\d+$/.test(name)) {
418+
return name;
419+
}
420+
421+
return quote(name, preferences);
422+
}
423+
413424
function isRecommendedCompletionMatch(localSymbol: Symbol, recommendedCompletion: Symbol | undefined, checker: TypeChecker): boolean {
414425
return localSymbol === recommendedCompletion ||
415426
!!(localSymbol.flags & SymbolFlags.ExportValue) && checker.getExportSymbolOfSymbol(localSymbol) === recommendedCompletion;
@@ -531,6 +542,7 @@ namespace ts.Completions {
531542
position: number,
532543
entryId: CompletionEntryIdentifier,
533544
host: LanguageServiceHost,
545+
preferences: UserPreferences,
534546
): SymbolCompletion | { type: "request", request: Request } | { type: "literal", literal: string | number | PseudoBigInt } | { type: "none" } {
535547
const compilerOptions = program.getCompilerOptions();
536548
const completionData = getCompletionData(program, log, sourceFile, isUncheckedFile(sourceFile, compilerOptions), position, { includeCompletionsForModuleExports: true, includeCompletionsWithInsertText: true }, entryId, host);
@@ -543,7 +555,7 @@ namespace ts.Completions {
543555

544556
const { symbols, literals, location, completionKind, symbolToOriginInfoMap, previousToken, isJsxInitializer, isTypeOnlyLocation } = completionData;
545557

546-
const literal = find(literals, l => completionNameForLiteral(l) === entryId.name);
558+
const literal = find(literals, l => completionNameForLiteral(l, preferences) === entryId.name);
547559
if (literal !== undefined) return { type: "literal", literal };
548560

549561
// Find the symbol with the matching entry name.
@@ -585,7 +597,7 @@ namespace ts.Completions {
585597
}
586598

587599
// Compute all the completion symbols again.
588-
const symbolCompletion = getSymbolCompletionFromEntryId(program, log, sourceFile, position, entryId, host);
600+
const symbolCompletion = getSymbolCompletionFromEntryId(program, log, sourceFile, position, entryId, host, preferences);
589601
switch (symbolCompletion.type) {
590602
case "request": {
591603
const { request } = symbolCompletion;
@@ -607,7 +619,7 @@ namespace ts.Completions {
607619
}
608620
case "literal": {
609621
const { literal } = symbolCompletion;
610-
return createSimpleDetails(completionNameForLiteral(literal), ScriptElementKind.string, typeof literal === "string" ? SymbolDisplayPartKind.stringLiteral : SymbolDisplayPartKind.numericLiteral);
622+
return createSimpleDetails(completionNameForLiteral(literal, preferences), ScriptElementKind.string, typeof literal === "string" ? SymbolDisplayPartKind.stringLiteral : SymbolDisplayPartKind.numericLiteral);
611623
}
612624
case "none":
613625
// Didn't find a symbol with this name. See if we can find a keyword instead.
@@ -677,8 +689,9 @@ namespace ts.Completions {
677689
position: number,
678690
entryId: CompletionEntryIdentifier,
679691
host: LanguageServiceHost,
692+
preferences: UserPreferences,
680693
): Symbol | undefined {
681-
const completion = getSymbolCompletionFromEntryId(program, log, sourceFile, position, entryId, host);
694+
const completion = getSymbolCompletionFromEntryId(program, log, sourceFile, position, entryId, host, preferences);
682695
return completion.type === "symbol" ? completion.symbol : undefined;
683696
}
684697

src/services/services.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1490,9 +1490,9 @@ namespace ts {
14901490
);
14911491
}
14921492

1493-
function getCompletionEntrySymbol(fileName: string, position: number, name: string, source?: string): Symbol | undefined {
1493+
function getCompletionEntrySymbol(fileName: string, position: number, name: string, source?: string, preferences: UserPreferences = emptyOptions): Symbol | undefined {
14941494
synchronizeHostData();
1495-
return Completions.getCompletionEntrySymbol(program, log, getValidSourceFile(fileName), position, { name, source }, host);
1495+
return Completions.getCompletionEntrySymbol(program, log, getValidSourceFile(fileName), position, { name, source }, host, preferences);
14961496
}
14971497

14981498
function getQuickInfoAtPosition(fileName: string, position: number): QuickInfo | undefined {

src/services/utilities.ts

-3
Original file line numberDiff line numberDiff line change
@@ -2343,9 +2343,6 @@ namespace ts {
23432343
}
23442344

23452345
export function quote(text: string, preferences: UserPreferences): string {
2346-
if (/^\d+$/.test(text)) {
2347-
return text;
2348-
}
23492346
// Editors can pass in undefined or empty string - we want to infer the preference in those cases.
23502347
const quotePreference = preferences.quotePreference || "auto";
23512348
const quoted = JSON.stringify(text);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
////enum A {
4+
//// A,
5+
//// B,
6+
//// C
7+
////}
8+
////interface B {
9+
//// a: keyof typeof A;
10+
////}
11+
////const b: B = {
12+
//// a: /**/
13+
////}
14+
15+
verify.completions({
16+
marker: "",
17+
includes: [
18+
{ name: "'A'" },
19+
{ name: "'B'" },
20+
{ name: "'C'" },
21+
],
22+
preferences: {
23+
quotePreference: 'single',
24+
},
25+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
////enum A {
4+
//// A,
5+
//// B,
6+
//// C
7+
////}
8+
////interface B {
9+
//// a: keyof typeof A;
10+
////}
11+
////const b: B = {
12+
//// a: /**/
13+
////}
14+
15+
verify.completions({
16+
marker: "",
17+
includes: [
18+
{ name: '"A"' },
19+
{ name: '"B"' },
20+
{ name: '"C"' },
21+
],
22+
preferences: { quotePreference: "double" },
23+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
////const a = {
4+
//// '#': 'a'
5+
////};
6+
////a[|./**/|]
7+
8+
verify.completions({
9+
marker: "",
10+
includes: [
11+
{ name: "#", insertText: "['#']", replacementSpan: test.ranges()[0] },
12+
],
13+
preferences: {
14+
includeInsertTextCompletions: true,
15+
quotePreference: "single",
16+
},
17+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
////const a = {
4+
//// "#": "a"
5+
////};
6+
////a[|./**/|]
7+
8+
verify.completions({
9+
marker: "",
10+
includes: [
11+
{ name: "#", insertText: '["#"]', replacementSpan: test.ranges()[0] },
12+
],
13+
preferences: {
14+
includeInsertTextCompletions: true,
15+
quotePreference: "double"
16+
},
17+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
////type T = 0 | 1;
4+
////const t: T = /**/
5+
6+
verify.completions({
7+
marker: "",
8+
includes: [
9+
{ name: "0" },
10+
{ name: "1" },
11+
],
12+
isNewIdentifierLocation: true
13+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
////type T = "0" | "1";
4+
////const t: T = /**/
5+
6+
verify.completions({
7+
marker: "",
8+
includes: [
9+
{ name: "'1'" },
10+
{ name: "'0'" },
11+
],
12+
isNewIdentifierLocation: true,
13+
preferences: { quotePreference: "single" }
14+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
////type T = "0" | "1";
4+
////const t: T = /**/
5+
6+
verify.completions({
7+
marker: "",
8+
includes: [
9+
{ name: '"1"' },
10+
{ name: '"0"' },
11+
],
12+
isNewIdentifierLocation: true,
13+
preferences: { quotePreference: "double" }
14+
});

0 commit comments

Comments
 (0)