From 422d845cc00d4358255eccfe641611393d6533f7 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Tue, 1 Mar 2022 15:45:31 -0800 Subject: [PATCH] Add test infrastructure --- src/harness/client.ts | 22 +++- src/harness/fourslashImpl.ts | 7 ++ src/harness/fourslashInterfaceImpl.ts | 4 + src/server/protocol.ts | 7 ++ src/server/session.ts | 111 +++++++++----------- tests/cases/fourslash/fourslash.ts | 2 + tests/cases/fourslash/server/goToSource1.ts | 13 +++ 7 files changed, 101 insertions(+), 65 deletions(-) create mode 100644 tests/cases/fourslash/server/goToSource1.ts diff --git a/src/harness/client.ts b/src/harness/client.ts index 537aa7321e5..06b954b2c83 100644 --- a/src/harness/client.ts +++ b/src/harness/client.ts @@ -300,7 +300,7 @@ namespace ts.server { getDefinitionAndBoundSpan(fileName: string, position: number): DefinitionInfoAndBoundSpan { const args: protocol.FileLocationRequestArgs = this.createFileLocationRequestArgs(fileName, position); - const request = this.processRequest(CommandNames.DefinitionAndBoundSpan, args); + const request = this.processRequest(CommandNames.DefinitionAndBoundSpan, args); const response = this.processResponse(request); const body = Debug.checkDefined(response.body); // TODO: GH#18217 @@ -334,6 +334,26 @@ namespace ts.server { })); } + getSourceDefinitionAndBoundSpan(fileName: string, position: number): DefinitionInfoAndBoundSpan { + const args: protocol.FileLocationRequestArgs = this.createFileLocationRequestArgs(fileName, position); + const request = this.processRequest(CommandNames.SourceDefinitionAndBoundSpan, args); + const response = this.processResponse(request); + const body = Debug.checkDefined(response.body); // TODO: GH#18217 + + return { + definitions: body.definitions.map(entry => ({ + containerKind: ScriptElementKind.unknown, + containerName: "", + fileName: entry.file, + textSpan: this.decodeSpan(entry), + kind: ScriptElementKind.unknown, + name: "", + unverified: entry.unverified, + })), + textSpan: this.decodeSpan(body.textSpan, request.arguments.file) + }; + } + getImplementationAtPosition(fileName: string, position: number): ImplementationLocation[] { const args = this.createFileLocationRequestArgs(fileName, position); diff --git a/src/harness/fourslashImpl.ts b/src/harness/fourslashImpl.ts index 47ed0e4a1ae..640e0e0b822 100644 --- a/src/harness/fourslashImpl.ts +++ b/src/harness/fourslashImpl.ts @@ -697,6 +697,13 @@ namespace FourSlash { this.verifyGoToX(arg0, endMarkerNames, () => this.getGoToDefinitionAndBoundSpan()); } + public verifyGoToSourceDefinition(startMarkerNames: ArrayOrSingle, end?: ArrayOrSingle | { file: string, unverified?: boolean }) { + if (this.testType !== FourSlashTestType.Server) { + this.raiseError("goToSourceDefinition may only be used in fourslash/server tests."); + } + this.verifyGoToX(startMarkerNames, end, () => (this.languageService as ts.server.SessionClient).getSourceDefinitionAndBoundSpan(this.activeFile.fileName, this.currentCaretPosition)!); + } + private getGoToDefinition(): readonly ts.DefinitionInfo[] { return this.languageService.getDefinitionAtPosition(this.activeFile.fileName, this.currentCaretPosition)!; } diff --git a/src/harness/fourslashInterfaceImpl.ts b/src/harness/fourslashInterfaceImpl.ts index 98b61cf2fb5..48cae7705f0 100644 --- a/src/harness/fourslashInterfaceImpl.ts +++ b/src/harness/fourslashInterfaceImpl.ts @@ -324,6 +324,10 @@ namespace FourSlashInterface { this.state.verifyGoToType(arg0, endMarkerName); } + public goToSourceDefinition(startMarkerNames: ArrayOrSingle, end: { file: string } | ArrayOrSingle) { + this.state.verifyGoToSourceDefinition(startMarkerNames, end); + } + public goToDefinitionForMarkers(...markerNames: string[]) { this.state.verifyGoToDefinitionForMarkers(markerNames); } diff --git a/src/server/protocol.ts b/src/server/protocol.ts index ca8dbd1cac6..56aaf000044 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -83,6 +83,9 @@ namespace ts.server.protocol { SignatureHelp = "signatureHelp", /* @internal */ SignatureHelpFull = "signatureHelp-full", + SourceDefinitionAndBoundSpan = "sourceDefinitionAndBoundSpan", + /* @internal */ + SourceDefinitionAndBoundSpanFull = "sourceDefinitionAndBoundSpan-full", Status = "status", TypeDefinition = "typeDefinition", ProjectInfo = "projectInfo", @@ -904,6 +907,10 @@ namespace ts.server.protocol { readonly command: CommandTypes.DefinitionAndBoundSpan; } + export interface SourceDefinitionAndBoundSpanRequest extends FileLocationRequest { + readonly command: CommandTypes.SourceDefinitionAndBoundSpan; + } + export interface DefinitionAndBoundSpanResponse extends Response { readonly body: DefinitionInfoAndBoundSpan; } diff --git a/src/server/session.ts b/src/server/session.ts index b87f0bdb9d3..81308329e64 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1239,36 +1239,6 @@ namespace ts.server { } private getDefinitionAndBoundSpan(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): protocol.DefinitionInfoAndBoundSpan | DefinitionInfoAndBoundSpan { - // const { file, project } = this.getFileAndProject(args); - // const position = this.getPositionInFile(args, file); - // const scriptInfo = Debug.checkDefined(project.getScriptInfo(file)); - - // const unmappedDefinitionAndBoundSpan = project.getLanguageService().getDefinitionAndBoundSpan(file, position); - - // if (!unmappedDefinitionAndBoundSpan || !unmappedDefinitionAndBoundSpan.definitions) { - // return { - // definitions: emptyArray, - // textSpan: undefined! // TODO: GH#18217 - // }; - // } - - // const definitions = this.mapDefinitionInfoLocations(unmappedDefinitionAndBoundSpan.definitions, project); - // const { textSpan } = unmappedDefinitionAndBoundSpan; - - // if (simplifiedResult) { - // return { - // definitions: this.mapDefinitionInfo(definitions, project), - // textSpan: toProtocolTextSpan(textSpan, scriptInfo) - // }; - // } - - // return { - // definitions: definitions.map(Session.mapToOriginalLocation), - // textSpan, - // }; - // } - - // private getSourceDefinitionAndBoundSpan(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): protocol.DefinitionInfoAndBoundSpan | DefinitionInfoAndBoundSpan { const { file, project } = this.getFileAndProject(args); const position = this.getPositionInFile(args, file); const scriptInfo = Debug.checkDefined(project.getScriptInfo(file)); @@ -1282,17 +1252,7 @@ namespace ts.server { }; } - const definitions = this.mapDefinitionInfoLocations(unmappedDefinitionAndBoundSpan.definitions, project).slice(); - const needsJsResolution = every(definitions.filter(d => d.isAliasTarget), d => !!d.isAmbient); - if (needsJsResolution) { - project.withAuxiliaryProjectForFiles([file], auxiliaryProject => { - const jsDefinitions = auxiliaryProject.getLanguageService().getDefinitionAndBoundSpan(file, position); - for (const jsDefinition of jsDefinitions?.definitions || emptyArray) { - pushIfUnique(definitions, jsDefinition, (a, b) => a.fileName === b.fileName && a.textSpan.start === b.textSpan.start); - } - }); - } - + const definitions = this.mapDefinitionInfoLocations(unmappedDefinitionAndBoundSpan.definitions, project); const { textSpan } = unmappedDefinitionAndBoundSpan; if (simplifiedResult) { @@ -1306,30 +1266,47 @@ namespace ts.server { definitions: definitions.map(Session.mapToOriginalLocation), textSpan, }; + } - // function getLocationsResolvingToDts(symbol: Symbol, checker: TypeChecker) { - // let locations: DocumentPosition[] | undefined; - // while (symbol.flags & SymbolFlags.Alias) { - // const aliasDeclaration = find(symbol.declarations || emptyArray, isAliasSymbolDeclaration); - // if (!aliasDeclaration) break; + private getSourceDefinitionAndBoundSpan(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): protocol.DefinitionInfoAndBoundSpan | DefinitionInfoAndBoundSpan { + const { file, project } = this.getFileAndProject(args); + const position = this.getPositionInFile(args, file); + const scriptInfo = Debug.checkDefined(project.getScriptInfo(file)); - // const resolvedSymbol = checker.getImmediateAliasedSymbol(symbol); - // if (!resolvedSymbol) break; + const unmappedDefinitionAndBoundSpan = project.getLanguageService().getDefinitionAndBoundSpan(file, position); - // const hasUnmappedDtsDeclarations = some(resolvedSymbol.declarations, d => { - // const sourceFile = getSourceFileOfNode(d); - // if (!isDeclarationFileName(sourceFile.fileName)) return false; - // const mappedFileName = project.getSourceMapper().tryGetSourcePosition(sourceFile)?.fileName; - // if (mappedFileName && project.projectService.fileExists(toNormalizedPath(mappedFileName))) return false; - // }); - // if (hasUnmappedDtsDeclarations) { - // const sourceFile = getSourceFileOfNode(aliasDeclaration); - // locations = append(locations, { fileName: sourceFile.fileName, pos: aliasDeclaration.getStart(sourceFile) }); - // } - // symbol = resolvedSymbol; - // } - // return locations; - // } + if (!unmappedDefinitionAndBoundSpan || !unmappedDefinitionAndBoundSpan.definitions) { + return { + definitions: emptyArray, + textSpan: undefined! // TODO: GH#18217 + }; + } + + let definitions = this.mapDefinitionInfoLocations(unmappedDefinitionAndBoundSpan.definitions, project).slice(); + const needsJsResolution = every(definitions.filter(d => d.isAliasTarget), d => !!d.isAmbient); + if (needsJsResolution) { + project.withAuxiliaryProjectForFiles([file], auxiliaryProject => { + const jsDefinitions = auxiliaryProject.getLanguageService().getDefinitionAndBoundSpan(file, position); + for (const jsDefinition of jsDefinitions?.definitions || emptyArray) { + pushIfUnique(definitions, jsDefinition, (a, b) => a.fileName === b.fileName && a.textSpan.start === b.textSpan.start); + } + }); + } + + definitions = definitions.filter(d => !d.isAmbient); + const { textSpan } = unmappedDefinitionAndBoundSpan; + + if (simplifiedResult) { + return { + definitions: this.mapDefinitionInfo(definitions, project), + textSpan: toProtocolTextSpan(textSpan, scriptInfo) + }; + } + + return { + definitions: definitions.map(Session.mapToOriginalLocation), + textSpan, + }; } private getEmitOutput(args: protocol.EmitOutputRequestArgs): EmitOutput | protocol.EmitOutput { @@ -2750,12 +2727,18 @@ namespace ts.server { [CommandNames.DefinitionFull]: (request: protocol.DefinitionRequest) => { return this.requiredResponse(this.getDefinition(request.arguments, /*simplifiedResult*/ false)); }, - [CommandNames.DefinitionAndBoundSpan]: (request: protocol.DefinitionRequest) => { + [CommandNames.DefinitionAndBoundSpan]: (request: protocol.DefinitionAndBoundSpanRequest) => { return this.requiredResponse(this.getDefinitionAndBoundSpan(request.arguments, /*simplifiedResult*/ true)); }, - [CommandNames.DefinitionAndBoundSpanFull]: (request: protocol.DefinitionRequest) => { + [CommandNames.DefinitionAndBoundSpanFull]: (request: protocol.DefinitionAndBoundSpanRequest) => { return this.requiredResponse(this.getDefinitionAndBoundSpan(request.arguments, /*simplifiedResult*/ false)); }, + [CommandNames.SourceDefinitionAndBoundSpan]: (request: protocol.SourceDefinitionAndBoundSpanRequest) => { + return this.requiredResponse(this.getSourceDefinitionAndBoundSpan(request.arguments, /*simplifiedResult*/ true)); + }, + [CommandNames.SourceDefinitionAndBoundSpanFull]: (request: protocol.SourceDefinitionAndBoundSpanRequest) => { + return this.requiredResponse(this.getSourceDefinitionAndBoundSpan(request.arguments, /*simplifiedResult*/ false)); + }, [CommandNames.EmitOutput]: (request: protocol.EmitOutputRequest) => { return this.requiredResponse(this.getEmitOutput(request.arguments)); }, diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 955d87c6f63..8990c6615a2 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -317,6 +317,8 @@ declare namespace FourSlashInterface { goToDefinition(startsAndEnds: { [startMarkerName: string]: ArrayOrSingle }): void; /** Verifies goToDefinition for each `${markerName}Reference` -> `${markerName}Definition` */ goToDefinitionForMarkers(...markerNames: string[]): void; + goToSourceDefinition(startMarkerNames: ArrayOrSingle, fileResult: { file: string }): void; + goToSourceDefinition(startMarkerNames: ArrayOrSingle, endMarkerNames: ArrayOrSingle): void; goToType(startsAndEnds: { [startMarkerName: string]: ArrayOrSingle }): void; goToType(startMarkerNames: ArrayOrSingle, endMarkerNames: ArrayOrSingle): void; verifyGetEmitOutputForCurrentFile(expected: string): void; diff --git a/tests/cases/fourslash/server/goToSource1.ts b/tests/cases/fourslash/server/goToSource1.ts new file mode 100644 index 00000000000..c9fbfb98fae --- /dev/null +++ b/tests/cases/fourslash/server/goToSource1.ts @@ -0,0 +1,13 @@ +/// + +// @Filename: /a.js +//// export const /*end*/a = "a"; + +// @Filename: /a.d.ts +//// export declare const a: string; + +// @Filename: /index.ts +//// import { a } from "./a"; +//// [|a/*start*/|] + +verify.goToSourceDefinition("start", "end");