fix(29881): infer quote preference for property access conversion in completions (#41041)

This commit is contained in:
Alex T
2020-10-13 20:06:27 +03:00
committed by GitHub
parent 7f6ad450bc
commit 417b8a9481
4 changed files with 37 additions and 24 deletions

View File

@@ -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);
}
}

View File

@@ -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.

View File

@@ -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 {

View File

@@ -0,0 +1,22 @@
/// <reference path='fourslash.ts'/>
// @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"
}
});