diff --git a/src/services/codefixes/fixInvalidJsxCharacters.ts b/src/services/codefixes/fixInvalidJsxCharacters.ts index 67ce26efd65..06c91c34953 100644 --- a/src/services/codefixes/fixInvalidJsxCharacters.ts +++ b/src/services/codefixes/fixInvalidJsxCharacters.ts @@ -42,7 +42,7 @@ namespace ts.codefix { return; } - const replacement = useHtmlEntity ? htmlEntity[character] : `{${quote(character, preferences)}}`; + const replacement = useHtmlEntity ? htmlEntity[character] : `{${quote(sourceFile, preferences, character)}}`; changes.replaceRangeWithText(sourceFile, { pos: start, end: start + 1 }, replacement); } } diff --git a/src/services/completions.ts b/src/services/completions.ts index eab6d030b01..40f3cac68c9 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -308,7 +308,7 @@ namespace ts.Completions { } for (const literal of literals) { - entries.push(createCompletionEntryForLiteral(literal, preferences)); + entries.push(createCompletionEntryForLiteral(sourceFile, preferences, literal)); } return { @@ -360,13 +360,13 @@ namespace ts.Completions { }); } - function completionNameForLiteral(literal: string | number | PseudoBigInt, preferences: UserPreferences): string { + function completionNameForLiteral(sourceFile: SourceFile, preferences: UserPreferences, literal: string | number | PseudoBigInt): string { return typeof literal === "object" ? pseudoBigIntToString(literal) + "n" : - isString(literal) ? quote(literal, preferences) : JSON.stringify(literal); + isString(literal) ? quote(sourceFile, preferences, literal) : JSON.stringify(literal); } - function createCompletionEntryForLiteral(literal: string | number | PseudoBigInt, preferences: UserPreferences): CompletionEntry { - return { name: completionNameForLiteral(literal, preferences), kind: ScriptElementKind.string, kindModifiers: ScriptElementKindModifier.none, sortText: SortText.LocationPriority }; + function createCompletionEntryForLiteral(sourceFile: SourceFile, preferences: UserPreferences, literal: string | number | PseudoBigInt): CompletionEntry { + return { name: completionNameForLiteral(sourceFile, preferences, literal), kind: ScriptElementKind.string, kindModifiers: ScriptElementKindModifier.none, sortText: SortText.LocationPriority }; } function createCompletionEntry( @@ -391,13 +391,13 @@ namespace ts.Completions { const useBraces = origin && originIsSymbolMember(origin) || needsConvertPropertyAccess; if (origin && originIsThisType(origin)) { insertText = needsConvertPropertyAccess - ? `this${insertQuestionDot ? "?." : ""}[${quotePropertyName(name, preferences)}]` + ? `this${insertQuestionDot ? "?." : ""}[${quotePropertyName(sourceFile, preferences, name)}]` : `this${insertQuestionDot ? "?." : "."}${name}`; } // We should only have needsConvertPropertyAccess if there's a property access to convert. But see #21790. // 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. else if ((useBraces || insertQuestionDot) && propertyAccessToConvert) { - insertText = useBraces ? needsConvertPropertyAccess ? `[${quotePropertyName(name, preferences)}]` : `[${name}]` : name; + insertText = useBraces ? needsConvertPropertyAccess ? `[${quotePropertyName(sourceFile, preferences, name)}]` : `[${name}]` : name; if (insertQuestionDot || propertyAccessToConvert.questionDotToken) { insertText = `?.${insertText}`; } @@ -458,12 +458,12 @@ namespace ts.Completions { }; } - function quotePropertyName(name: string, preferences: UserPreferences): string { + function quotePropertyName(sourceFile: SourceFile, preferences: UserPreferences, name: string,): string { if (/^\d+$/.test(name)) { return name; } - return quote(name, preferences); + return quote(sourceFile, preferences, name); } function isRecommendedCompletionMatch(localSymbol: Symbol, recommendedCompletion: Symbol | undefined, checker: TypeChecker): boolean { @@ -614,7 +614,7 @@ namespace ts.Completions { const { symbols, literals, location, completionKind, symbolToOriginInfoMap, previousToken, isJsxInitializer, isTypeOnlyLocation } = completionData; - const literal = find(literals, l => completionNameForLiteral(l, preferences) === entryId.name); + const literal = find(literals, l => completionNameForLiteral(sourceFile, preferences, l) === entryId.name); if (literal !== undefined) return { type: "literal", literal }; // Find the symbol with the matching entry name. @@ -678,7 +678,7 @@ namespace ts.Completions { } case "literal": { const { literal } = symbolCompletion; - return createSimpleDetails(completionNameForLiteral(literal, preferences), ScriptElementKind.string, typeof literal === "string" ? SymbolDisplayPartKind.stringLiteral : SymbolDisplayPartKind.numericLiteral); + return createSimpleDetails(completionNameForLiteral(sourceFile, preferences, literal), ScriptElementKind.string, typeof literal === "string" ? SymbolDisplayPartKind.stringLiteral : SymbolDisplayPartKind.numericLiteral); } case "none": // Didn't find a symbol with this name. See if we can find a keyword instead. diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 294b8326ec0..264cad7e557 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -2473,20 +2473,11 @@ namespace ts { } } - export function quote(text: string, preferences: UserPreferences): string { + export function quote(sourceFile: SourceFile, preferences: UserPreferences, text: string): string { // Editors can pass in undefined or empty string - we want to infer the preference in those cases. - const quotePreference = preferences.quotePreference || "auto"; + const quotePreference = getQuotePreference(sourceFile, preferences); const quoted = JSON.stringify(text); - switch (quotePreference) { - // TODO use getQuotePreference to infer the actual quote style. - case "auto": - case "double": - return quoted; - case "single": - return `'${stripQuotes(quoted).replace("'", "\\'").replace('\\"', '"')}'`; - default: - return Debug.assertNever(quotePreference); - } + return quotePreference === QuotePreference.Single ? `'${stripQuotes(quoted).replace("'", "\\'").replace('\\"', '"')}'` : quoted; } export function isEqualityOperatorKind(kind: SyntaxKind): kind is EqualityOperator { diff --git a/tests/cases/fourslash/completionForStringLiteral_quotePreference7.ts b/tests/cases/fourslash/completionForStringLiteral_quotePreference7.ts new file mode 100644 index 00000000000..a37014ffa1c --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteral_quotePreference7.ts @@ -0,0 +1,22 @@ +/// + +// @filename: /a.ts +////export const a = null; + +// @filename: /b.ts +////import { a } from './a'; +//// +////const foo = { '#': null }; +////foo[|./**/|] + +goTo.file("/b.ts"); +verify.completions({ + marker: "", + exact: [ + { name: "#", insertText: "['#']", replacementSpan: test.ranges()[0] }, + ], + preferences: { + includeInsertTextCompletions: true, + quotePreference: "auto" + } +});