Account for trigger reasons, as implemented by Roslyn.

This commit is contained in:
Daniel Rosenwasser
2018-07-02 16:04:48 -07:00
parent c046958752
commit 9651c4231e
6 changed files with 175 additions and 42 deletions
+28 -14
View File
@@ -1426,17 +1426,22 @@ Actual: ${stringify(fullActual)}`);
}
}
public verifyNoSignatureHelp(triggerCharacter: ts.SignatureHelpTriggerCharacter | undefined, markers: ReadonlyArray<string>) {
public verifySignatureHelpPresence(expectPresent: boolean, triggerReason: ts.SignatureHelpTriggerReason | undefined, markers: ReadonlyArray<string>) {
if (markers.length) {
for (const marker of markers) {
this.goToMarker(marker);
this.verifyNoSignatureHelp(triggerCharacter, ts.emptyArray);
this.verifySignatureHelpPresence(expectPresent, triggerReason, ts.emptyArray);
}
return;
}
const actual = this.getSignatureHelp({ triggerCharacter });
if (actual) {
this.raiseError(`Expected no signature help, but got "${stringify(actual)}"`);
const actual = this.getSignatureHelp({ triggerReason });
if (expectPresent !== !!actual) {
if (actual) {
this.raiseError(`Expected no signature help, but got "${stringify(actual)}"`);
}
else {
this.raiseError("Expected signature help, but none was returned.")
}
}
}
@@ -1455,7 +1460,7 @@ Actual: ${stringify(fullActual)}`);
}
private verifySignatureHelpWorker(options: FourSlashInterface.VerifySignatureHelpOptions) {
const help = this.getSignatureHelp({ triggerCharacter: options.triggerCharacter })!;
const help = this.getSignatureHelp({ triggerReason: options.triggerReason })!;
const selectedItem = help.items[help.selectedItemIndex];
// Argument index may exceed number of parameters
const currentParameter = selectedItem.parameters[help.argumentIndex] as ts.SignatureHelpParameter | undefined;
@@ -1497,7 +1502,7 @@ Actual: ${stringify(fullActual)}`);
const allKeys: ReadonlyArray<keyof FourSlashInterface.VerifySignatureHelpOptions> = [
"marker",
"triggerCharacter",
"triggerReason",
"overloadsCount",
"docComment",
"text",
@@ -1769,9 +1774,9 @@ Actual: ${stringify(fullActual)}`);
Harness.IO.log(stringify(help.items[help.selectedItemIndex]));
}
private getSignatureHelp({ triggerCharacter }: FourSlashInterface.VerifySignatureHelpOptions): ts.SignatureHelpItems | undefined {
private getSignatureHelp({ triggerReason }: FourSlashInterface.VerifySignatureHelpOptions): ts.SignatureHelpItems | undefined {
return this.languageService.getSignatureHelpItems(this.activeFile.fileName, this.currentCaretPosition, {
triggerCharacter
triggerReason
});
}
@@ -1870,7 +1875,12 @@ Actual: ${stringify(fullActual)}`);
if (highFidelity) {
if (ch === "(" || ch === "," || ch === "<") {
/* Signature help*/
this.languageService.getSignatureHelpItems(this.activeFile.fileName, offset, { triggerCharacter: ch });
this.languageService.getSignatureHelpItems(this.activeFile.fileName, offset, {
triggerReason: {
kind: "characterTyped",
triggerCharacter: ch
}
});
}
else if (prevChar === " " && /A-Za-z_/.test(ch)) {
/* Completions */
@@ -4081,11 +4091,15 @@ namespace FourSlashInterface {
}
public noSignatureHelp(...markers: string[]): void {
this.state.verifyNoSignatureHelp(/*triggerCharacter*/ undefined, markers);
this.state.verifySignatureHelpPresence(/*expectPresent*/ false, /*triggerReason*/ undefined, markers);
}
public noSignatureHelpForTriggerCharacter(triggerCharacter: ts.SignatureHelpTriggerCharacter, ...markers: string[]): void {
this.state.verifyNoSignatureHelp(triggerCharacter, markers);
public noSignatureHelpForTriggerReason(reason: ts.SignatureHelpTriggerReason, ...markers: string[]): void {
this.state.verifySignatureHelpPresence(/*expectPresent*/ false, reason, markers);
}
public signatureHelpPresentForTriggerReason(reason: ts.SignatureHelpTriggerReason, ...markers: string[]): void {
this.state.verifySignatureHelpPresence(/*expectPresent*/ true, reason, markers);
}
public signatureHelp(...options: VerifySignatureHelpOptions[]): void {
@@ -4816,7 +4830,7 @@ namespace FourSlashInterface {
readonly isVariadic?: boolean;
/** @default ts.emptyArray */
readonly tags?: ReadonlyArray<ts.JSDocTagInfo>;
readonly triggerCharacter?: ts.SignatureHelpTriggerCharacter;
readonly triggerReason?: ts.SignatureHelpTriggerReason;
}
export type ArrayOrSingle<T> = T | ReadonlyArray<T>;
+47 -6
View File
@@ -2067,16 +2067,57 @@ namespace ts.server.protocol {
}
export type SignatureHelpTriggerCharacter = "," | "(" | "<";
export type SignatureHelpRetriggerCharacter = SignatureHelpTriggerCharacter | ")";
/**
* Arguments of a signature help request.
*/
/**
* Arguments of a signature help request.
*/
export interface SignatureHelpRequestArgs extends FileLocationRequestArgs {
/**
* Character that was responsible for triggering signature help.
* Should be `undefined` if a user manually requested completion.
* Reason why signature help was invoked.
* See each individual possible
*/
triggerCharacter?: SignatureHelpTriggerCharacter;
triggerReason?: SignatureHelpTriggerReason;
}
export type SignatureHelpTriggerReason =
| SignatureHelpInvokedReason
| SignatureHelpCharacterTypedReason
| SignatureHelpRetriggeredReason;
/**
* Signals that the user manually requested signature help.
* The language service will unconditionally attempt to provide a result.
*/
export interface SignatureHelpInvokedReason {
kind: "invoked",
triggerCharacter?: undefined,
}
/**
* Signals that the signature help request came from a user typing a character.
* Depending on the character and the syntactic context, the request may or may not be served a result.
*/
export interface SignatureHelpCharacterTypedReason {
kind: "characterTyped",
/**
* Character that was responsible for triggering signature help.
*/
triggerCharacter: SignatureHelpTriggerCharacter,
}
/**
* Signals that this signature help request came from typing a character or moving the cursor.
* This should only occur if a signature help session was already active and the editor needs to see if it should adjust.
* The language service will unconditionally attempt to provide a result.
* `triggerCharacter` can be `undefined` for a retrigger caused by a cursor move.
*/
export interface SignatureHelpRetriggeredReason {
kind: "retrigger",
/**
* Character that was responsible for triggering signature help.
*/
triggerCharacter?: SignatureHelpRetriggerCharacter,
}
/**
+2 -2
View File
@@ -1756,12 +1756,12 @@ namespace ts {
/**
* This is a semantic operation.
*/
function getSignatureHelpItems(fileName: string, position: number, { triggerCharacter }: SignatureHelpItemsOptions = emptyOptions): SignatureHelpItems | undefined {
function getSignatureHelpItems(fileName: string, position: number, { triggerReason }: SignatureHelpItemsOptions = emptyOptions): SignatureHelpItems | undefined {
synchronizeHostData();
const sourceFile = getValidSourceFile(fileName);
return SignatureHelp.getSignatureHelpItems(program, sourceFile, position, triggerCharacter, cancellationToken);
return SignatureHelp.getSignatureHelpItems(program, sourceFile, position, triggerReason, cancellationToken);
}
/// Syntactic features
+11 -4
View File
@@ -19,7 +19,7 @@ namespace ts.SignatureHelp {
argumentCount: number;
}
export function getSignatureHelpItems(program: Program, sourceFile: SourceFile, position: number, triggerCharacter: SignatureHelpTriggerCharacter | undefined, cancellationToken: CancellationToken): SignatureHelpItems | undefined {
export function getSignatureHelpItems(program: Program, sourceFile: SourceFile, position: number, triggerReason: SignatureHelpTriggerReason | undefined, cancellationToken: CancellationToken): SignatureHelpItems | undefined {
const typeChecker = program.getTypeChecker();
// Decide whether to show signature help
@@ -29,9 +29,11 @@ namespace ts.SignatureHelp {
return undefined;
}
// In the middle of a string, don't provide signature help unless the user explicitly requested it.
if (triggerCharacter !== undefined && isInString(sourceFile, position, startingToken)) {
return undefined;
if (shouldCarefullyCheckContext(triggerReason)) {
// In the middle of a string, don't provide signature help unless the user explicitly requested it.
if (isInString(sourceFile, position, startingToken)) {
return undefined;
}
}
const argumentInfo = getContainingArgumentInfo(startingToken, position, sourceFile);
@@ -55,6 +57,11 @@ namespace ts.SignatureHelp {
return typeChecker.runWithCancellationToken(cancellationToken, typeChecker => createSignatureHelpItems(candidateInfo.candidates, candidateInfo.resolvedSignature, argumentInfo, sourceFile, typeChecker));
}
function shouldCarefullyCheckContext(reason: SignatureHelpTriggerReason | undefined) {
// Only need to be careful if the user typed a character and signature help wasn't showing.
return !!reason && reason.kind === "characterTyped";
}
function getCandidateInfo(argumentInfo: ArgumentListInfo, checker: TypeChecker): { readonly candidates: ReadonlyArray<Signature>, readonly resolvedSignature: Signature } | undefined {
const { invocation } = argumentInfo;
if (invocation.kind === InvocationKind.Call) {
+40 -3
View File
@@ -383,13 +383,50 @@ namespace ts {
}
export type SignatureHelpTriggerCharacter = "," | "(" | "<";
export type SignatureHelpRetriggerCharacter = SignatureHelpTriggerCharacter | ")";
export interface SignatureHelpItemsOptions {
triggerReason?: SignatureHelpTriggerReason;
}
export type SignatureHelpTriggerReason =
| SignatureHelpInvokedReason
| SignatureHelpCharacterTypedReason
| SignatureHelpRetriggeredReason;
/**
* Signals that the user manually requested signature help.
* The language service will unconditionally attempt to provide a result.
*/
export interface SignatureHelpInvokedReason {
kind: "invoked",
triggerCharacter?: undefined,
}
/**
* Signals that the signature help request came from a user typing a character.
* Depending on the character and the syntactic context, the request may or may not be served a result.
*/
export interface SignatureHelpCharacterTypedReason {
kind: "characterTyped",
/**
* If the editor is asking for signature help because a certain character was typed
* (as opposed to when the user explicitly requested them) this should be set.
* Character that was responsible for triggering signature help.
*/
triggerCharacter?: SignatureHelpTriggerCharacter;
triggerCharacter: SignatureHelpTriggerCharacter,
}
/**
* Signals that this signature help request came from typing a character or moving the cursor.
* This should only occur if a signature help session was already active and the editor needs to see if it should adjust.
* The language service will unconditionally attempt to provide a result.
* `triggerCharacter` can be `undefined` for a retrigger caused by a cursor move.
*/
export interface SignatureHelpRetriggeredReason {
kind: "retrigger",
/**
* Character that was responsible for triggering signature help.
*/
triggerCharacter?: SignatureHelpRetriggerCharacter,
}
export interface ApplyCodeActionCommandResult {
+47 -13
View File
@@ -139,7 +139,6 @@ declare namespace FourSlashInterface {
file(name: string, content?: string, scriptKindName?: string): any;
select(startMarker: string, endMarker: string): void;
selectRange(range: Range): void;
selectAllInFile(fileName: string): void;
}
class verifyNegatable {
private negative;
@@ -179,7 +178,7 @@ declare namespace FourSlashInterface {
isInCommentAtPosition(onlyMultiLineDiverges?: boolean): void;
codeFix(options: {
description: string,
newFileContent?: NewFileContent,
newFileContent?: string | { readonly [fileName: string]: string },
newRangeContent?: string,
errorCode?: number,
index?: number,
@@ -191,7 +190,6 @@ declare namespace FourSlashInterface {
applicableRefactorAvailableForRange(): void;
refactorAvailable(name: string, actionName?: string): void;
refactorsAvailable(names: ReadonlyArray<string>): void;
refactor(options: {
name: string;
actionName: string;
@@ -257,14 +255,15 @@ declare namespace FourSlashInterface {
* For each of starts, asserts the ranges that are referenced from there.
* This uses the 'findReferences' command instead of 'getReferencesAtPosition', so references are grouped by their definition.
*/
referenceGroups(starts: ArrayOrSingle<string> | ArrayOrSingle<Range>, parts: Array<FourSlashInterface.ReferenceGroup>): void;
referenceGroups(starts: ArrayOrSingle<string> | ArrayOrSingle<Range>, parts: Array<{ definition: ReferencesDefinition, ranges: Range[] }>): void;
singleReferenceGroup(definition: ReferencesDefinition, ranges?: Range[]): void;
rangesAreOccurrences(isWriteAccess?: boolean): void;
rangesWithSameTextAreRenameLocations(): void;
rangesAreRenameLocations(options?: Range[] | { findInStrings?: boolean, findInComments?: boolean, ranges?: Range[] });
findReferencesDefinitionDisplayPartsAtCaretAre(expected: ts.SymbolDisplayPart[]): void;
noSignatureHelp(...markers: string[]): void;
noSignatureHelpForTriggerCharacter(triggerCharacter: string, ...markers: string[]): void
noSignatureHelpForTriggerReason(triggerReason: SignatureHelpTriggerReason, ...markers: string[]): void
signatureHelpPresentForTriggerReason(triggerReason: SignatureHelpTriggerReason, ...markers: string[]): void
signatureHelp(...options: VerifySignatureHelpOptions[], ): void;
// Checks that there are no compile errors.
noErrors(): void;
@@ -338,7 +337,7 @@ declare namespace FourSlashInterface {
getEditsForFileRename(options: {
oldPath: string;
newPath: string;
newFileContents: { readonly [fileName: string]: string };
newFileContents: { [fileName: string]: string };
}): void;
moveToNewFile(options: {
readonly newFileContents: { readonly [fileName: string]: string };
@@ -359,7 +358,7 @@ declare namespace FourSlashInterface {
enableFormatting(): void;
disableFormatting(): void;
applyRefactor(options: { refactorName: string, actionName: string, actionDescription: string, newContent: NewFileContent }): void;
applyRefactor(options: { refactorName: string, actionName: string, actionDescription: string, newContent: string }): void;
}
class debug {
printCurrentParameterHelp(): void;
@@ -514,10 +513,6 @@ declare namespace FourSlashInterface {
text: string;
range: Range;
}
interface ReferenceGroup {
readonly definition: ReferencesDefinition;
readonly ranges: ReadonlyArray<Range>;
}
interface Diagnostic {
message: string;
/** @default `test.ranges()[0]` */
@@ -567,7 +562,47 @@ declare namespace FourSlashInterface {
argumentCount?: number;
isVariadic?: boolean;
tags?: ReadonlyArray<JSDocTagInfo>;
triggerCharacter?: string;
triggerReason?: SignatureHelpTriggerReason;
}
export type SignatureHelpTriggerReason =
| SignatureHelpInvokedReason
| SignatureHelpCharacterTypedReason
| SignatureHelpRetriggeredReason;
/**
* Signals that the user manually requested signature help.
* The language service will unconditionally attempt to provide a result.
*/
export interface SignatureHelpInvokedReason {
kind: "invoked",
triggerCharacter?: undefined,
}
/**
* Signals that the signature help request came from a user typing a character.
* Depending on the character and the syntactic context, the request may or may not be served a result.
*/
export interface SignatureHelpCharacterTypedReason {
kind: "characterTyped",
/**
* Character that was responsible for triggering signature help.
*/
triggerCharacter: string,
}
/**
* Signals that this signature help request came from typing a character or moving the cursor.
* This should only occur if a signature help session was already active and the editor needs to see if it should adjust.
* The language service will unconditionally attempt to provide a result.
* `triggerCharacter` can be `undefined` for a retrigger caused by a cursor move.
*/
export interface SignatureHelpRetriggeredReason {
kind: "retrigger",
/**
* Character that was responsible for triggering signature help.
*/
triggerCharacter?: string,
}
interface JSDocTagInfo {
@@ -576,7 +611,6 @@ declare namespace FourSlashInterface {
}
type ArrayOrSingle<T> = T | ReadonlyArray<T>;
type NewFileContent = string | { readonly [fileName: string]: string };
}
declare function verifyOperationIsCancelled(f: any): void;
declare var test: FourSlashInterface.test_;