fix(33377): use quoteStyle option for string literals completions (#36720)

This commit is contained in:
Alexander T 2020-02-20 18:45:07 +02:00 committed by GitHub
parent d76380419d
commit c8147cb601
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 149 additions and 16 deletions

View file

@ -272,7 +272,7 @@ namespace ts.Completions {
}
for (const literal of literals) {
entries.push(createCompletionEntryForLiteral(literal));
entries.push(createCompletionEntryForLiteral(literal, preferences));
}
return { isGlobalCompletion: isInSnippetScope, isMemberCompletion: isMemberCompletionKind(completionKind), isNewIdentifierLocation, entries };
@ -317,10 +317,13 @@ namespace ts.Completions {
});
}
const completionNameForLiteral = (literal: string | number | PseudoBigInt) =>
typeof literal === "object" ? pseudoBigIntToString(literal) + "n" : JSON.stringify(literal);
function createCompletionEntryForLiteral(literal: string | number | PseudoBigInt): CompletionEntry {
return { name: completionNameForLiteral(literal), kind: ScriptElementKind.string, kindModifiers: ScriptElementKindModifier.none, sortText: SortText.LocationPriority };
function completionNameForLiteral(literal: string | number | PseudoBigInt, preferences: UserPreferences): string {
return typeof literal === "object" ? pseudoBigIntToString(literal) + "n" :
isString(literal) ? quote(literal, preferences) : 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 createCompletionEntry(
@ -344,13 +347,13 @@ namespace ts.Completions {
const useBraces = origin && originIsSymbolMember(origin) || needsConvertPropertyAccess;
if (origin && originIsThisType(origin)) {
insertText = needsConvertPropertyAccess
? `this${insertQuestionDot ? "?." : ""}[${quote(name, preferences)}]`
? `this${insertQuestionDot ? "?." : ""}[${quotePropertyName(name, preferences)}]`
: `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 ? `[${quote(name, preferences)}]` : `[${name}]` : name;
insertText = useBraces ? needsConvertPropertyAccess ? `[${quotePropertyName(name, preferences)}]` : `[${name}]` : name;
if (insertQuestionDot || propertyAccessToConvert.questionDotToken) {
insertText = `?.${insertText}`;
}
@ -410,6 +413,14 @@ namespace ts.Completions {
};
}
function quotePropertyName(name: string, preferences: UserPreferences): string {
if (/^\d+$/.test(name)) {
return name;
}
return quote(name, preferences);
}
function isRecommendedCompletionMatch(localSymbol: Symbol, recommendedCompletion: Symbol | undefined, checker: TypeChecker): boolean {
return localSymbol === recommendedCompletion ||
!!(localSymbol.flags & SymbolFlags.ExportValue) && checker.getExportSymbolOfSymbol(localSymbol) === recommendedCompletion;
@ -531,6 +542,7 @@ namespace ts.Completions {
position: number,
entryId: CompletionEntryIdentifier,
host: LanguageServiceHost,
preferences: UserPreferences,
): SymbolCompletion | { type: "request", request: Request } | { type: "literal", literal: string | number | PseudoBigInt } | { type: "none" } {
const compilerOptions = program.getCompilerOptions();
const completionData = getCompletionData(program, log, sourceFile, isUncheckedFile(sourceFile, compilerOptions), position, { includeCompletionsForModuleExports: true, includeCompletionsWithInsertText: true }, entryId, host);
@ -543,7 +555,7 @@ namespace ts.Completions {
const { symbols, literals, location, completionKind, symbolToOriginInfoMap, previousToken, isJsxInitializer, isTypeOnlyLocation } = completionData;
const literal = find(literals, l => completionNameForLiteral(l) === entryId.name);
const literal = find(literals, l => completionNameForLiteral(l, preferences) === entryId.name);
if (literal !== undefined) return { type: "literal", literal };
// Find the symbol with the matching entry name.
@ -585,7 +597,7 @@ namespace ts.Completions {
}
// Compute all the completion symbols again.
const symbolCompletion = getSymbolCompletionFromEntryId(program, log, sourceFile, position, entryId, host);
const symbolCompletion = getSymbolCompletionFromEntryId(program, log, sourceFile, position, entryId, host, preferences);
switch (symbolCompletion.type) {
case "request": {
const { request } = symbolCompletion;
@ -607,7 +619,7 @@ namespace ts.Completions {
}
case "literal": {
const { literal } = symbolCompletion;
return createSimpleDetails(completionNameForLiteral(literal), ScriptElementKind.string, typeof literal === "string" ? SymbolDisplayPartKind.stringLiteral : SymbolDisplayPartKind.numericLiteral);
return createSimpleDetails(completionNameForLiteral(literal, preferences), 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.
@ -677,8 +689,9 @@ namespace ts.Completions {
position: number,
entryId: CompletionEntryIdentifier,
host: LanguageServiceHost,
preferences: UserPreferences,
): Symbol | undefined {
const completion = getSymbolCompletionFromEntryId(program, log, sourceFile, position, entryId, host);
const completion = getSymbolCompletionFromEntryId(program, log, sourceFile, position, entryId, host, preferences);
return completion.type === "symbol" ? completion.symbol : undefined;
}

View file

@ -1490,9 +1490,9 @@ namespace ts {
);
}
function getCompletionEntrySymbol(fileName: string, position: number, name: string, source?: string): Symbol | undefined {
function getCompletionEntrySymbol(fileName: string, position: number, name: string, source?: string, preferences: UserPreferences = emptyOptions): Symbol | undefined {
synchronizeHostData();
return Completions.getCompletionEntrySymbol(program, log, getValidSourceFile(fileName), position, { name, source }, host);
return Completions.getCompletionEntrySymbol(program, log, getValidSourceFile(fileName), position, { name, source }, host, preferences);
}
function getQuickInfoAtPosition(fileName: string, position: number): QuickInfo | undefined {

View file

@ -2343,9 +2343,6 @@ namespace ts {
}
export function quote(text: string, preferences: UserPreferences): string {
if (/^\d+$/.test(text)) {
return text;
}
// Editors can pass in undefined or empty string - we want to infer the preference in those cases.
const quotePreference = preferences.quotePreference || "auto";
const quoted = JSON.stringify(text);

View file

@ -0,0 +1,25 @@
/// <reference path='fourslash.ts'/>
////enum A {
//// A,
//// B,
//// C
////}
////interface B {
//// a: keyof typeof A;
////}
////const b: B = {
//// a: /**/
////}
verify.completions({
marker: "",
includes: [
{ name: "'A'" },
{ name: "'B'" },
{ name: "'C'" },
],
preferences: {
quotePreference: 'single',
},
});

View file

@ -0,0 +1,23 @@
/// <reference path='fourslash.ts'/>
////enum A {
//// A,
//// B,
//// C
////}
////interface B {
//// a: keyof typeof A;
////}
////const b: B = {
//// a: /**/
////}
verify.completions({
marker: "",
includes: [
{ name: '"A"' },
{ name: '"B"' },
{ name: '"C"' },
],
preferences: { quotePreference: "double" },
});

View file

@ -0,0 +1,17 @@
/// <reference path='fourslash.ts'/>
////const a = {
//// '#': 'a'
////};
////a[|./**/|]
verify.completions({
marker: "",
includes: [
{ name: "#", insertText: "['#']", replacementSpan: test.ranges()[0] },
],
preferences: {
includeInsertTextCompletions: true,
quotePreference: "single",
},
});

View file

@ -0,0 +1,17 @@
/// <reference path='fourslash.ts'/>
////const a = {
//// "#": "a"
////};
////a[|./**/|]
verify.completions({
marker: "",
includes: [
{ name: "#", insertText: '["#"]', replacementSpan: test.ranges()[0] },
],
preferences: {
includeInsertTextCompletions: true,
quotePreference: "double"
},
});

View file

@ -0,0 +1,13 @@
/// <reference path='fourslash.ts'/>
////type T = 0 | 1;
////const t: T = /**/
verify.completions({
marker: "",
includes: [
{ name: "0" },
{ name: "1" },
],
isNewIdentifierLocation: true
});

View file

@ -0,0 +1,14 @@
/// <reference path='fourslash.ts'/>
////type T = "0" | "1";
////const t: T = /**/
verify.completions({
marker: "",
includes: [
{ name: "'1'" },
{ name: "'0'" },
],
isNewIdentifierLocation: true,
preferences: { quotePreference: "single" }
});

View file

@ -0,0 +1,14 @@
/// <reference path='fourslash.ts'/>
////type T = "0" | "1";
////const t: T = /**/
verify.completions({
marker: "",
includes: [
{ name: '"1"' },
{ name: '"0"' },
],
isNewIdentifierLocation: true,
preferences: { quotePreference: "double" }
});