diff --git a/src/server/session.ts b/src/server/session.ts index fe57abe6b98b1..385a1d3abe93d 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1433,7 +1433,7 @@ namespace ts.server { return project.getLanguageService().getCompletionEntryDetails(file, position, name, formattingOptions, source, this.getPreferences(file)); }); return simplifiedResult - ? result.map(details => ({ ...details, codeActions: map(details.codeActions, action => this.mapCodeAction(project, action)) })) + ? result.map(details => ({ ...details, codeActions: map(details.codeActions, action => this.mapCodeAction(action)) })) : result; } @@ -1735,7 +1735,7 @@ namespace ts.server { const renameScriptInfo = project.getScriptInfoForNormalizedPath(toNormalizedPath(renameFilename))!; mappedRenameLocation = getLocationInNewDocument(getSnapshotText(renameScriptInfo.getSnapshot()), renameFilename, renameLocation, edits); } - return { renameLocation: mappedRenameLocation, renameFilename, edits: this.mapTextChangesToCodeEdits(project, edits) }; + return { renameLocation: mappedRenameLocation, renameFilename, edits: this.mapTextChangesToCodeEdits(edits) }; } else { return result; @@ -1747,7 +1747,7 @@ namespace ts.server { const { file, project } = this.getFileAndProject(scope.args); const changes = project.getLanguageService().organizeImports({ type: "file", fileName: file }, this.getFormatOptions(file), this.getPreferences(file)); if (simplifiedResult) { - return this.mapTextChangesToCodeEdits(project, changes); + return this.mapTextChangesToCodeEdits(changes); } else { return changes; @@ -1763,7 +1763,7 @@ namespace ts.server { this.projectService, project => project.getLanguageService().getEditsForFileRename(oldPath, newPath, formatOptions, preferences), (a, b) => a.fileName === b.fileName); - return simplifiedResult ? changes.map(c => this.mapTextChangeToCodeEditUsingScriptInfoOrConfigFile(c)) : changes; + return simplifiedResult ? changes.map(c => this.mapTextChangeToCodeEdit(c)) : changes; } private getCodeFixes(args: protocol.CodeFixRequestArgs, simplifiedResult: boolean): ReadonlyArray | ReadonlyArray | undefined { @@ -1776,7 +1776,7 @@ namespace ts.server { const { startPosition, endPosition } = this.getStartAndEndPosition(args, scriptInfo); const codeActions = project.getLanguageService().getCodeFixesAtPosition(file, startPosition, endPosition, args.errorCodes!, this.getFormatOptions(file), this.getPreferences(file)); - return simplifiedResult ? codeActions.map(codeAction => this.mapCodeFixAction(project, codeAction)) : codeActions; + return simplifiedResult ? codeActions.map(codeAction => this.mapCodeFixAction(codeAction)) : codeActions; } private getCombinedCodeFix({ scope, fixId }: protocol.GetCombinedCodeFixRequestArgs, simplifiedResult: boolean): protocol.CombinedCodeActions | CombinedCodeActions { @@ -1784,7 +1784,7 @@ namespace ts.server { const { file, project } = this.getFileAndProject(scope.args); const res = project.getLanguageService().getCombinedCodeFix({ type: "file", fileName: file }, fixId, this.getFormatOptions(file), this.getPreferences(file)); if (simplifiedResult) { - return { changes: this.mapTextChangesToCodeEdits(project, res.changes), commands: res.commands }; + return { changes: this.mapTextChangesToCodeEdits(res.changes), commands: res.commands }; } else { return res; @@ -1824,28 +1824,20 @@ namespace ts.server { return { startPosition, endPosition }; } - private mapCodeAction(project: Project, { description, changes, commands }: CodeAction): protocol.CodeAction { - return { description, changes: this.mapTextChangesToCodeEdits(project, changes), commands }; + private mapCodeAction({ description, changes, commands }: CodeAction): protocol.CodeAction { + return { description, changes: this.mapTextChangesToCodeEdits(changes), commands }; } - private mapCodeFixAction(project: Project, { fixName, description, changes, commands, fixId, fixAllDescription }: CodeFixAction): protocol.CodeFixAction { - return { fixName, description, changes: this.mapTextChangesToCodeEdits(project, changes), commands, fixId, fixAllDescription }; + private mapCodeFixAction({ fixName, description, changes, commands, fixId, fixAllDescription }: CodeFixAction): protocol.CodeFixAction { + return { fixName, description, changes: this.mapTextChangesToCodeEdits(changes), commands, fixId, fixAllDescription }; } - private mapTextChangesToCodeEdits(project: Project, textChanges: ReadonlyArray): protocol.FileCodeEdits[] { - return textChanges.map(change => this.mapTextChangeToCodeEdit(project, change)); + private mapTextChangesToCodeEdits(textChanges: ReadonlyArray): protocol.FileCodeEdits[] { + return textChanges.map(change => this.mapTextChangeToCodeEdit(change)); } - private mapTextChangeToCodeEdit(project: Project, change: FileTextChanges): protocol.FileCodeEdits { - return mapTextChangesToCodeEditsForFile(change, project.getSourceFileOrConfigFile(this.normalizePath(change.fileName))); - } - - private mapTextChangeToCodeEditUsingScriptInfoOrConfigFile(change: FileTextChanges): protocol.FileCodeEdits { - return mapTextChangesToCodeEditsUsingScriptInfoOrConfig(change, this.projectService.getScriptInfoOrConfig(this.normalizePath(change.fileName))); - } - - private normalizePath(fileName: string) { - return normalizedPathToPath(toNormalizedPath(fileName), this.host.getCurrentDirectory(), fileName => this.getCanonicalFileName(fileName)); + private mapTextChangeToCodeEdit(change: FileTextChanges): protocol.FileCodeEdits { + return mapTextChangesToCodeEdits(change, this.projectService.getScriptInfoOrConfig(change.fileName)); } private convertTextChangeToCodeEdit(change: TextChange, scriptInfo: ScriptInfo): protocol.CodeEdit { @@ -2357,35 +2349,14 @@ namespace ts.server { return { file: fileName, start: scriptInfo.positionToLineOffset(textSpan.start), end: scriptInfo.positionToLineOffset(textSpanEnd(textSpan)) }; } - function mapTextChangesToCodeEditsForFile(textChanges: FileTextChanges, sourceFile: SourceFile | undefined): protocol.FileCodeEdits { - Debug.assert(!!textChanges.isNewFile === !sourceFile, "Expected isNewFile for (only) new files", () => JSON.stringify({ isNewFile: !!textChanges.isNewFile, hasSourceFile: !!sourceFile })); - if (sourceFile) { - return { - fileName: textChanges.fileName, - textChanges: textChanges.textChanges.map(textChange => convertTextChangeToCodeEdit(textChange, sourceFile)), - }; - } - else { - return convertNewFileTextChangeToCodeEdit(textChanges); - } - } - - function mapTextChangesToCodeEditsUsingScriptInfoOrConfig(textChanges: FileTextChanges, scriptInfo: ScriptInfoOrConfig | undefined): protocol.FileCodeEdits { + function mapTextChangesToCodeEdits(textChanges: FileTextChanges, scriptInfo: ScriptInfoOrConfig | undefined): protocol.FileCodeEdits { Debug.assert(!!textChanges.isNewFile === !scriptInfo, "Expected isNewFile for (only) new files", () => JSON.stringify({ isNewFile: !!textChanges.isNewFile, hasScriptInfo: !!scriptInfo })); return scriptInfo - ? { fileName: textChanges.fileName, textChanges: textChanges.textChanges.map(textChange => convertTextChangeToCodeEditUsingScriptInfoOrConfig(textChange, scriptInfo)) } + ? { fileName: textChanges.fileName, textChanges: textChanges.textChanges.map(textChange => convertTextChangeToCodeEdit(textChange, scriptInfo)) } : convertNewFileTextChangeToCodeEdit(textChanges); } - function convertTextChangeToCodeEdit(change: TextChange, sourceFile: SourceFile): protocol.CodeEdit { - return { - start: convertToLocation(sourceFile.getLineAndCharacterOfPosition(change.span.start)), - end: convertToLocation(sourceFile.getLineAndCharacterOfPosition(change.span.start + change.span.length)), - newText: change.newText ? change.newText : "", - }; - } - - function convertTextChangeToCodeEditUsingScriptInfoOrConfig(change: TextChange, scriptInfo: ScriptInfoOrConfig): protocol.CodeEdit { + function convertTextChangeToCodeEdit(change: TextChange, scriptInfo: ScriptInfoOrConfig): protocol.CodeEdit { return { start: positionToLineOffset(scriptInfo, change.span.start), end: positionToLineOffset(scriptInfo, textSpanEnd(change.span)), newText: change.newText }; } diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index 00cbda971515a..ff5ee24bf5d27 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -515,6 +515,10 @@ namespace ts.projectSystem { return session.executeCommand(makeSessionRequest(command, args)).response as TResponse["body"]; } + export function executeSessionRequestNoResponse(session: server.Session, command: TRequest["command"], args: TRequest["arguments"]): void { + session.executeCommand(makeSessionRequest(command, args)); + } + export function openFilesForSession(files: ReadonlyArray, session: server.Session): void { for (const file of files) { session.executeCommand(makeSessionRequest(CommandNames.Open, @@ -9256,6 +9260,50 @@ export function Test2() { }); }); + describe("Untitled files", () => { + it("Can convert positions to locations", () => { + const aTs: File = { path: "/proj/a.ts", content: "" }; + const tsconfig: File = { path: "/proj/tsconfig.json", content: "{}" }; + const session = createSession(createServerHost([aTs, tsconfig])); + + openFilesForSession([aTs], session); + + const untitledFile = "untitled:^Untitled-1"; + executeSessionRequestNoResponse(session, protocol.CommandTypes.Open, { + file: untitledFile, + fileContent: "let foo = 1;\nfooo/**/", + scriptKindName: "TS", + projectRootPath: "/proj", + }); + + const response = executeSessionRequest(session, protocol.CommandTypes.GetCodeFixes, { + file: untitledFile, + startLine: 2, + startOffset: 1, + endLine: 2, + endOffset: 5, + errorCodes: [Diagnostics.Cannot_find_name_0_Did_you_mean_1.code], + }); + assert.deepEqual | undefined>(response, [ + { + description: "Change spelling to 'foo'", + fixAllDescription: "Fix all detected spelling errors", + fixId: "fixSpelling", + fixName: "spelling", + changes: [{ + fileName: untitledFile, + textChanges: [{ + start: { line: 2, offset: 1 }, + end: { line: 2, offset: 5 }, + newText: "foo", + }], + }], + commands: undefined, + }, + ]); + }); + }); + function makeReferenceItem(file: File, isDefinition: boolean, text: string, lineText: string, options?: SpanFromSubstringOptions): protocol.ReferencesResponseItem { return { ...protocolFileSpanFromSubstring(file, text, options), diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 57deee2ab9856..4b9c0307810ec 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -8893,8 +8893,6 @@ declare namespace ts.server { private mapCodeFixAction; private mapTextChangesToCodeEdits; private mapTextChangeToCodeEdit; - private mapTextChangeToCodeEditUsingScriptInfoOrConfigFile; - private normalizePath; private convertTextChangeToCodeEdit; private getBraceMatching; private getDiagnosticsForProject;