Fix replacementSpan for class member snippet completion entries (#52231)

This commit is contained in:
Gabriela Araujo Britto
2023-01-16 10:32:24 -08:00
committed by GitHub
parent 4ba756fdc8
commit 67a3846fbf
6 changed files with 59 additions and 11 deletions
+5 -1
View File
@@ -984,8 +984,9 @@ export class TestState {
if (actual.insertText !== expected.insertText) {
this.raiseError(`At entry ${actual.name}: Completion insert text did not match: ${showTextDiff(expected.insertText || "", actual.insertText || "")}`);
}
const convertedReplacementSpan = expected.replacementSpan && ts.createTextSpanFromRange(expected.replacementSpan);
if (convertedReplacementSpan?.length) {
if (convertedReplacementSpan) {
try {
assert.deepEqual(actual.replacementSpan, convertedReplacementSpan);
}
@@ -993,6 +994,9 @@ export class TestState {
this.raiseError(`At entry ${actual.name}: Expected completion replacementSpan to be ${stringify(convertedReplacementSpan)}, got ${stringify(actual.replacementSpan)}`);
}
}
else if (ts.hasProperty(expected, "replacementSpan")) { // Expected `replacementSpan` is explicitly set as `undefined`.
assert.equal(actual.replacementSpan, undefined, `At entry ${actual.name}: Expected 'replacementSpan' properties to match`);
}
if (expected.kind !== undefined || expected.kindModifiers !== undefined) {
assert.equal(actual.kind, expected.kind, `At entry ${actual.name}: Expected 'kind' for ${actual.name} to match`);
+16 -4
View File
@@ -927,6 +927,7 @@ function completionInfoFromData(
/*replacementToken*/ undefined,
contextToken,
location,
position,
sourceFile,
host,
program,
@@ -1316,6 +1317,7 @@ function createCompletionEntry(
replacementToken: Node | undefined,
contextToken: Node | undefined,
location: Node,
position: number,
sourceFile: SourceFile,
host: LanguageServiceHost,
program: Program,
@@ -1406,7 +1408,8 @@ function createCompletionEntry(
completionKind === CompletionKind.MemberLike &&
isClassLikeMemberCompletion(symbol, location, sourceFile)) {
let importAdder;
({ insertText, isSnippet, importAdder, replacementSpan } = getEntryForMemberCompletion(host, program, options, preferences, name, symbol, location, contextToken, formatContext));
({ insertText, isSnippet, importAdder, replacementSpan } =
getEntryForMemberCompletion(host, program, options, preferences, name, symbol, location, position, contextToken, formatContext));
sortText = SortText.ClassMemberSnippets; // sortText has to be lower priority than the sortText for keywords. See #47852.
if (importAdder?.hasFixes()) {
hasAction = true;
@@ -1545,6 +1548,7 @@ function getEntryForMemberCompletion(
name: string,
symbol: Symbol,
location: Node,
position: number,
contextToken: Node | undefined,
formatContext: formatting.FormatContext | undefined,
): { insertText: string, isSnippet?: true, importAdder?: codefix.ImportAdder, replacementSpan?: TextSpan } {
@@ -1586,7 +1590,7 @@ function getEntryForMemberCompletion(
let modifiers = ModifierFlags.None;
// Whether the suggested member should be abstract.
// e.g. in `abstract class C { abstract | }`, we should offer abstract method signatures at position `|`.
const { modifiers: presentModifiers, span: modifiersSpan } = getPresentModifiers(contextToken);
const { modifiers: presentModifiers, span: modifiersSpan } = getPresentModifiers(contextToken, sourceFile, position);
const isAbstract = !!(presentModifiers & ModifierFlags.Abstract);
const completionNodes: Node[] = [];
codefix.addNewNodeForMemberSymbol(
@@ -1650,8 +1654,13 @@ function getEntryForMemberCompletion(
return { insertText, isSnippet, importAdder, replacementSpan };
}
function getPresentModifiers(contextToken: Node | undefined): { modifiers: ModifierFlags, span?: TextSpan } {
if (!contextToken) {
function getPresentModifiers(
contextToken: Node | undefined,
sourceFile: SourceFile,
position: number): { modifiers: ModifierFlags, span?: TextSpan } {
if (!contextToken ||
getLineAndCharacterOfPosition(sourceFile, position).line
> getLineAndCharacterOfPosition(sourceFile, contextToken.getEnd()).line) {
return { modifiers: ModifierFlags.None };
}
let modifiers = ModifierFlags.None;
@@ -2086,6 +2095,7 @@ export function getCompletionEntriesFromSymbols(
replacementToken: Node | undefined,
contextToken: Node | undefined,
location: Node,
position: number,
sourceFile: SourceFile,
host: LanguageServiceHost,
program: Program,
@@ -2132,6 +2142,7 @@ export function getCompletionEntriesFromSymbols(
replacementToken,
contextToken,
location,
position,
sourceFile,
host,
program,
@@ -2471,6 +2482,7 @@ function getCompletionEntryCodeActionsAndSourceDisplay(
name,
symbol,
location,
position,
contextToken,
formatContext);
if (importAdder) {
+3 -1
View File
@@ -198,7 +198,7 @@ export function getStringLiteralCompletions(
if (isInString(sourceFile, position, contextToken)) {
if (!contextToken || !isStringLiteralLike(contextToken)) return undefined;
const entries = getStringLiteralCompletionEntries(sourceFile, contextToken, position, program.getTypeChecker(), options, host, preferences);
return convertStringLiteralCompletions(entries, contextToken, sourceFile, host, program, log, options, preferences);
return convertStringLiteralCompletions(entries, contextToken, sourceFile, host, program, log, options, preferences, position);
}
}
@@ -211,6 +211,7 @@ function convertStringLiteralCompletions(
log: Log,
options: CompilerOptions,
preferences: UserPreferences,
position: number,
): CompletionInfo | undefined {
if (completion === undefined) {
return undefined;
@@ -228,6 +229,7 @@ function convertStringLiteralCompletions(
contextToken,
contextToken,
sourceFile,
position,
sourceFile,
host,
program,
@@ -173,10 +173,6 @@
"kindModifiers": "",
"sortText": "17",
"insertText": "method(): void {\r\n}",
"replacementSpan": {
"start": 71,
"length": 16
},
"displayParts": [
{
"text": "(",
@@ -0,0 +1,34 @@
/// <reference path="fourslash.ts" />
// @Filename: a.ts
// @newline: LF
// Issue #52211
//// interface Interface {
//// method(): void;
//// }
////
//// export class Class implements Interface {
//// property = "yadda";
////
//// /**/
//// }
verify.completions({
marker: "",
isNewIdentifierLocation: true,
preferences: {
includeCompletionsWithInsertText: true,
includeCompletionsWithSnippetText: false,
includeCompletionsWithClassMemberSnippets: true,
},
includes: [
{
name: "method",
sortText: completion.SortText.ClassMemberSnippets,
insertText: "method(): void {\n}",
replacementSpan: undefined,
},
],
});
@@ -20,7 +20,7 @@
//// [|import f/*4*/ =|]
// @Filename: /index5.ts
//// import f/*5*/ from "";
//// [|import f/*5*/ from "";|]
([[0, true], [1, true], [2, false], [3, true], [4, true], [5, true]] as const).forEach(([marker, typeKeywordValid]) => {
verify.completions({