Skip to content

Commit 417b8a9

Browse files
authored
fix(29881): infer quote preference for property access conversion in completions (microsoft#41041)
1 parent 7f6ad45 commit 417b8a9

File tree

4 files changed

+37
-24
lines changed

4 files changed

+37
-24
lines changed

src/services/codefixes/fixInvalidJsxCharacters.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ namespace ts.codefix {
4242
return;
4343
}
4444

45-
const replacement = useHtmlEntity ? htmlEntity[character] : `{${quote(character, preferences)}}`;
45+
const replacement = useHtmlEntity ? htmlEntity[character] : `{${quote(sourceFile, preferences, character)}}`;
4646
changes.replaceRangeWithText(sourceFile, { pos: start, end: start + 1 }, replacement);
4747
}
4848
}

src/services/completions.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ namespace ts.Completions {
308308
}
309309

310310
for (const literal of literals) {
311-
entries.push(createCompletionEntryForLiteral(literal, preferences));
311+
entries.push(createCompletionEntryForLiteral(sourceFile, preferences, literal));
312312
}
313313

314314
return {
@@ -360,13 +360,13 @@ namespace ts.Completions {
360360
});
361361
}
362362

363-
function completionNameForLiteral(literal: string | number | PseudoBigInt, preferences: UserPreferences): string {
363+
function completionNameForLiteral(sourceFile: SourceFile, preferences: UserPreferences, literal: string | number | PseudoBigInt): string {
364364
return typeof literal === "object" ? pseudoBigIntToString(literal) + "n" :
365-
isString(literal) ? quote(literal, preferences) : JSON.stringify(literal);
365+
isString(literal) ? quote(sourceFile, preferences, literal) : JSON.stringify(literal);
366366
}
367367

368-
function createCompletionEntryForLiteral(literal: string | number | PseudoBigInt, preferences: UserPreferences): CompletionEntry {
369-
return { name: completionNameForLiteral(literal, preferences), kind: ScriptElementKind.string, kindModifiers: ScriptElementKindModifier.none, sortText: SortText.LocationPriority };
368+
function createCompletionEntryForLiteral(sourceFile: SourceFile, preferences: UserPreferences, literal: string | number | PseudoBigInt): CompletionEntry {
369+
return { name: completionNameForLiteral(sourceFile, preferences, literal), kind: ScriptElementKind.string, kindModifiers: ScriptElementKindModifier.none, sortText: SortText.LocationPriority };
370370
}
371371

372372
function createCompletionEntry(
@@ -391,13 +391,13 @@ namespace ts.Completions {
391391
const useBraces = origin && originIsSymbolMember(origin) || needsConvertPropertyAccess;
392392
if (origin && originIsThisType(origin)) {
393393
insertText = needsConvertPropertyAccess
394-
? `this${insertQuestionDot ? "?." : ""}[${quotePropertyName(name, preferences)}]`
394+
? `this${insertQuestionDot ? "?." : ""}[${quotePropertyName(sourceFile, preferences, name)}]`
395395
: `this${insertQuestionDot ? "?." : "."}${name}`;
396396
}
397397
// We should only have needsConvertPropertyAccess if there's a property access to convert. But see #21790.
398398
// 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.
399399
else if ((useBraces || insertQuestionDot) && propertyAccessToConvert) {
400-
insertText = useBraces ? needsConvertPropertyAccess ? `[${quotePropertyName(name, preferences)}]` : `[${name}]` : name;
400+
insertText = useBraces ? needsConvertPropertyAccess ? `[${quotePropertyName(sourceFile, preferences, name)}]` : `[${name}]` : name;
401401
if (insertQuestionDot || propertyAccessToConvert.questionDotToken) {
402402
insertText = `?.${insertText}`;
403403
}
@@ -458,12 +458,12 @@ namespace ts.Completions {
458458
};
459459
}
460460

461-
function quotePropertyName(name: string, preferences: UserPreferences): string {
461+
function quotePropertyName(sourceFile: SourceFile, preferences: UserPreferences, name: string,): string {
462462
if (/^\d+$/.test(name)) {
463463
return name;
464464
}
465465

466-
return quote(name, preferences);
466+
return quote(sourceFile, preferences, name);
467467
}
468468

469469
function isRecommendedCompletionMatch(localSymbol: Symbol, recommendedCompletion: Symbol | undefined, checker: TypeChecker): boolean {
@@ -614,7 +614,7 @@ namespace ts.Completions {
614614

615615
const { symbols, literals, location, completionKind, symbolToOriginInfoMap, previousToken, isJsxInitializer, isTypeOnlyLocation } = completionData;
616616

617-
const literal = find(literals, l => completionNameForLiteral(l, preferences) === entryId.name);
617+
const literal = find(literals, l => completionNameForLiteral(sourceFile, preferences, l) === entryId.name);
618618
if (literal !== undefined) return { type: "literal", literal };
619619

620620
// Find the symbol with the matching entry name.
@@ -678,7 +678,7 @@ namespace ts.Completions {
678678
}
679679
case "literal": {
680680
const { literal } = symbolCompletion;
681-
return createSimpleDetails(completionNameForLiteral(literal, preferences), ScriptElementKind.string, typeof literal === "string" ? SymbolDisplayPartKind.stringLiteral : SymbolDisplayPartKind.numericLiteral);
681+
return createSimpleDetails(completionNameForLiteral(sourceFile, preferences, literal), ScriptElementKind.string, typeof literal === "string" ? SymbolDisplayPartKind.stringLiteral : SymbolDisplayPartKind.numericLiteral);
682682
}
683683
case "none":
684684
// Didn't find a symbol with this name. See if we can find a keyword instead.

src/services/utilities.ts

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2473,20 +2473,11 @@ namespace ts {
24732473
}
24742474
}
24752475

2476-
export function quote(text: string, preferences: UserPreferences): string {
2476+
export function quote(sourceFile: SourceFile, preferences: UserPreferences, text: string): string {
24772477
// Editors can pass in undefined or empty string - we want to infer the preference in those cases.
2478-
const quotePreference = preferences.quotePreference || "auto";
2478+
const quotePreference = getQuotePreference(sourceFile, preferences);
24792479
const quoted = JSON.stringify(text);
2480-
switch (quotePreference) {
2481-
// TODO use getQuotePreference to infer the actual quote style.
2482-
case "auto":
2483-
case "double":
2484-
return quoted;
2485-
case "single":
2486-
return `'${stripQuotes(quoted).replace("'", "\\'").replace('\\"', '"')}'`;
2487-
default:
2488-
return Debug.assertNever(quotePreference);
2489-
}
2480+
return quotePreference === QuotePreference.Single ? `'${stripQuotes(quoted).replace("'", "\\'").replace('\\"', '"')}'` : quoted;
24902481
}
24912482

24922483
export function isEqualityOperatorKind(kind: SyntaxKind): kind is EqualityOperator {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
// @filename: /a.ts
4+
////export const a = null;
5+
6+
// @filename: /b.ts
7+
////import { a } from './a';
8+
////
9+
////const foo = { '#': null };
10+
////foo[|./**/|]
11+
12+
goTo.file("/b.ts");
13+
verify.completions({
14+
marker: "",
15+
exact: [
16+
{ name: "#", insertText: "['#']", replacementSpan: test.ranges()[0] },
17+
],
18+
preferences: {
19+
includeInsertTextCompletions: true,
20+
quotePreference: "auto"
21+
}
22+
});

0 commit comments

Comments
 (0)