Add optionalReplacementSpan to completions response (#40347)

* Add optionalReplacementRange to completions response

* Get the name right

* Fix unit tests

* Fix comment typo

* Fix comment typo

* Baseline
This commit is contained in:
Andrew Branch 2020-09-04 13:09:52 -07:00 committed by GitHub
parent 8384018e68
commit f6f2d36ee3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 76 additions and 5 deletions

View File

@ -830,6 +830,13 @@ namespace FourSlash {
this.raiseError(`Expected 'isGlobalCompletion to be ${options.isGlobalCompletion}, got ${actualCompletions.isGlobalCompletion}`);
}
if (ts.hasProperty(options, "optionalReplacementSpan")) {
assert.deepEqual(
actualCompletions.optionalReplacementSpan && actualCompletions.optionalReplacementSpan,
options.optionalReplacementSpan && ts.createTextSpanFromRange(options.optionalReplacementSpan),
"Expected 'optionalReplacementSpan' properties to match");
}
const nameToEntries = new ts.Map<string, ts.CompletionEntry[]>();
for (const entry of actualCompletions.entries) {
const entries = nameToEntries.get(entry.name);

View File

@ -1529,6 +1529,7 @@ namespace FourSlashInterface {
readonly marker?: ArrayOrSingle<string | FourSlash.Marker>;
readonly isNewIdentifierLocation?: boolean; // Always tested
readonly isGlobalCompletion?: boolean; // Only tested if set
readonly optionalReplacementSpan?: FourSlash.Range; // Only tested if set
readonly exact?: ArrayOrSingle<ExpectedCompletionEntry>;
readonly includes?: ArrayOrSingle<ExpectedCompletionEntry>;
readonly excludes?: ArrayOrSingle<string>;

View File

@ -2256,6 +2256,12 @@ namespace ts.server.protocol {
readonly isGlobalCompletion: boolean;
readonly isMemberCompletion: boolean;
readonly isNewIdentifierLocation: boolean;
/**
* In the absence of `CompletionEntry["replacementSpan"]`, the editor may choose whether to use
* this span or its default one. If `CompletionEntry["replacementSpan"]` is defined, that span
* must be used to commit that completion entry.
*/
readonly optionalReplacementSpan?: TextSpan;
readonly entries: readonly CompletionEntry[];
}

View File

@ -1783,6 +1783,7 @@ namespace ts.server {
const res: protocol.CompletionInfo = {
...completions,
optionalReplacementSpan: completions.optionalReplacementSpan && toProtocolTextSpan(completions.optionalReplacementSpan, scriptInfo),
entries,
};
return res;

View File

@ -209,6 +209,11 @@ namespace ts.Completions {
return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, entries };
}
function getOptionalReplacementSpan(location: Node | undefined) {
// StringLiteralLike locations are handled separately in stringCompletions.ts
return location?.kind === SyntaxKind.Identifier ? createTextSpanFromNode(location) : undefined;
}
function completionInfoFromData(sourceFile: SourceFile, typeChecker: TypeChecker, compilerOptions: CompilerOptions, log: Log, completionData: CompletionData, preferences: UserPreferences): CompletionInfo | undefined {
const {
symbols,
@ -241,7 +246,7 @@ namespace ts.Completions {
kindModifiers: undefined,
sortText: SortText.LocationPriority,
};
return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: false, entries: [entry] };
return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: false, optionalReplacementSpan: getOptionalReplacementSpan(location), entries: [entry] };
}
const entries: CompletionEntry[] = [];
@ -305,7 +310,13 @@ namespace ts.Completions {
entries.push(createCompletionEntryForLiteral(literal, preferences));
}
return { isGlobalCompletion: isInSnippetScope, isMemberCompletion: isMemberCompletionKind(completionKind), isNewIdentifierLocation, entries };
return {
isGlobalCompletion: isInSnippetScope,
isMemberCompletion: isMemberCompletionKind(completionKind),
isNewIdentifierLocation,
optionalReplacementSpan: getOptionalReplacementSpan(location),
entries
};
}
function isUncheckedFile(sourceFile: SourceFile, compilerOptions: CompilerOptions): boolean {

View File

@ -12,10 +12,12 @@ namespace ts.Completions.StringCompletions {
}
}
function convertStringLiteralCompletions(completion: StringLiteralCompletion | undefined, contextToken: Node, sourceFile: SourceFile, checker: TypeChecker, log: Log, preferences: UserPreferences): CompletionInfo | undefined {
function convertStringLiteralCompletions(completion: StringLiteralCompletion | undefined, contextToken: StringLiteralLike, sourceFile: SourceFile, checker: TypeChecker, log: Log, preferences: UserPreferences): CompletionInfo | undefined {
if (completion === undefined) {
return undefined;
}
const optionalReplacementSpan = createTextSpanFromStringLiteralLikeContent(contextToken);
switch (completion.kind) {
case StringLiteralCompletionKind.Paths:
return convertPathCompletions(completion.paths);
@ -33,7 +35,7 @@ namespace ts.Completions.StringCompletions {
CompletionKind.String,
preferences
); // Target will not be used, so arbitrary
return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: completion.hasIndexSignature, entries };
return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: completion.hasIndexSignature, optionalReplacementSpan, entries };
}
case StringLiteralCompletionKind.Types: {
const entries = completion.types.map(type => ({
@ -43,7 +45,7 @@ namespace ts.Completions.StringCompletions {
sortText: "0",
replacementSpan: getReplacementSpanForContextToken(contextToken)
}));
return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: completion.isNewIdentifier, entries };
return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: completion.isNewIdentifier, optionalReplacementSpan, entries };
}
default:
return Debug.assertNever(completion);

View File

@ -1087,6 +1087,12 @@ namespace ts {
/** Not true for all global completions. This will be true if the enclosing scope matches a few syntax kinds. See `isSnippetScope`. */
isGlobalCompletion: boolean;
isMemberCompletion: boolean;
/**
* In the absence of `CompletionEntry["replacementSpan"], the editor may choose whether to use
* this span or its default one. If `CompletionEntry["replacementSpan"]` is defined, that span
* must be used to commit that completion entry.
*/
optionalReplacementSpan?: TextSpan;
/**
* true when the current location also allows for a new identifier

View File

@ -44,6 +44,7 @@ namespace ts.projectSystem {
isGlobalCompletion: true,
isMemberCompletion: false,
isNewIdentifierLocation: false,
optionalReplacementSpan: { start: { line: 1, offset: 1 }, end: { line: 1, offset: 4 } },
entries: [entry],
});

View File

@ -80,6 +80,10 @@ namespace ts.projectSystem {
isGlobalCompletion: false,
isMemberCompletion: true,
isNewIdentifierLocation: false,
optionalReplacementSpan: {
start: { line: 1, offset: aTs.content.indexOf("prop;") + 1 },
end: { line: 1, offset: aTs.content.indexOf("prop;") + 1 + "prop".length }
},
entries: expectedCompletionEntries
});
});

View File

@ -5964,6 +5964,12 @@ declare namespace ts {
/** Not true for all global completions. This will be true if the enclosing scope matches a few syntax kinds. See `isSnippetScope`. */
isGlobalCompletion: boolean;
isMemberCompletion: boolean;
/**
* In the absence of `CompletionEntry["replacementSpan"], the editor may choose whether to use
* this span or its default one. If `CompletionEntry["replacementSpan"]` is defined, that span
* must be used to commit that completion entry.
*/
optionalReplacementSpan?: TextSpan;
/**
* true when the current location also allows for a new identifier
*/
@ -8102,6 +8108,12 @@ declare namespace ts.server.protocol {
readonly isGlobalCompletion: boolean;
readonly isMemberCompletion: boolean;
readonly isNewIdentifierLocation: boolean;
/**
* In the absence of `CompletionEntry["replacementSpan"]`, the editor may choose whether to use
* this span or its default one. If `CompletionEntry["replacementSpan"]` is defined, that span
* must be used to commit that completion entry.
*/
readonly optionalReplacementSpan?: TextSpan;
readonly entries: readonly CompletionEntry[];
}
interface CompletionDetailsResponse extends Response {

View File

@ -5964,6 +5964,12 @@ declare namespace ts {
/** Not true for all global completions. This will be true if the enclosing scope matches a few syntax kinds. See `isSnippetScope`. */
isGlobalCompletion: boolean;
isMemberCompletion: boolean;
/**
* In the absence of `CompletionEntry["replacementSpan"], the editor may choose whether to use
* this span or its default one. If `CompletionEntry["replacementSpan"]` is defined, that span
* must be used to commit that completion entry.
*/
optionalReplacementSpan?: TextSpan;
/**
* true when the current location also allows for a new identifier
*/

View File

@ -0,0 +1,13 @@
/// <reference path="fourslash.ts" />
// @lib: dom
//// console.[|cl/*0*/ockwork|];
//// type T = Array["[|toS/*1*/paghetti|]"];
test.ranges().forEach((range, marker) => {
verify.completions({
marker: `${marker}`,
optionalReplacementSpan: range,
});
});

View File

@ -610,6 +610,7 @@ declare namespace FourSlashInterface {
readonly marker?: ArrayOrSingle<string | Marker>;
readonly isNewIdentifierLocation?: boolean;
readonly isGlobalCompletion?: boolean;
readonly optionalReplacementSpan?: Range;
readonly exact?: ArrayOrSingle<ExpectedCompletionEntry>;
readonly includes?: ArrayOrSingle<ExpectedCompletionEntry>;
readonly excludes?: ArrayOrSingle<string>;