1namespace ts.server { 2 interface StackTraceError extends Error { 3 stack?: string; 4 } 5 6 export interface ServerCancellationToken extends HostCancellationToken { 7 setRequest(requestId: number): void; 8 resetRequest(requestId: number): void; 9 } 10 11 export const nullCancellationToken: ServerCancellationToken = { 12 isCancellationRequested: () => false, 13 setRequest: () => void 0, 14 resetRequest: () => void 0 15 }; 16 17 function hrTimeToMilliseconds(time: number[]): number { 18 const seconds = time[0]; 19 const nanoseconds = time[1]; 20 return ((1e9 * seconds) + nanoseconds) / 1000000.0; 21 } 22 23 function isDeclarationFileInJSOnlyNonConfiguredProject(project: Project, file: NormalizedPath) { 24 // Checking for semantic diagnostics is an expensive process. We want to avoid it if we 25 // know for sure it is not needed. 26 // For instance, .d.ts files injected by ATA automatically do not produce any relevant 27 // errors to a JS- only project. 28 // 29 // Note that configured projects can set skipLibCheck (on by default in jsconfig.json) to 30 // disable checking for declaration files. We only need to verify for inferred projects (e.g. 31 // miscellaneous context in VS) and external projects(e.g.VS.csproj project) with only JS 32 // files. 33 // 34 // We still want to check .js files in a JS-only inferred or external project (e.g. if the 35 // file has '// @ts-check'). 36 37 if ((isInferredProject(project) || isExternalProject(project)) && 38 project.isJsOnlyProject()) { 39 const scriptInfo = project.getScriptInfoForNormalizedPath(file); 40 return scriptInfo && !scriptInfo.isJavaScript(); 41 } 42 return false; 43 } 44 45 46 function dtsChangeCanAffectEmit(compilationSettings: CompilerOptions) { 47 return getEmitDeclarations(compilationSettings) || !!compilationSettings.emitDecoratorMetadata; 48 } 49 50 function formatDiag(fileName: NormalizedPath, project: Project, diag: Diagnostic): protocol.Diagnostic { 51 const scriptInfo = project.getScriptInfoForNormalizedPath(fileName)!; // TODO: GH#18217 52 return { 53 start: scriptInfo.positionToLineOffset(diag.start!), 54 end: scriptInfo.positionToLineOffset(diag.start! + diag.length!), // TODO: GH#18217 55 text: flattenDiagnosticMessageText(diag.messageText, "\n"), 56 code: diag.code, 57 category: diagnosticCategoryName(diag), 58 reportsUnnecessary: diag.reportsUnnecessary, 59 reportsDeprecated: diag.reportsDeprecated, 60 source: diag.source, 61 relatedInformation: map(diag.relatedInformation, formatRelatedInformation), 62 }; 63 } 64 65 function formatRelatedInformation(info: DiagnosticRelatedInformation): protocol.DiagnosticRelatedInformation { 66 if (!info.file) { 67 return { 68 message: flattenDiagnosticMessageText(info.messageText, "\n"), 69 category: diagnosticCategoryName(info), 70 code: info.code 71 }; 72 } 73 return { 74 span: { 75 start: convertToLocation(getLineAndCharacterOfPosition(info.file, info.start!)), 76 end: convertToLocation(getLineAndCharacterOfPosition(info.file, info.start! + info.length!)), // TODO: GH#18217 77 file: info.file.fileName 78 }, 79 message: flattenDiagnosticMessageText(info.messageText, "\n"), 80 category: diagnosticCategoryName(info), 81 code: info.code 82 }; 83 } 84 85 function convertToLocation(lineAndCharacter: LineAndCharacter): protocol.Location { 86 return { line: lineAndCharacter.line + 1, offset: lineAndCharacter.character + 1 }; 87 } 88 89 function formatDiagnosticToProtocol(diag: Diagnostic, includeFileName: true): protocol.DiagnosticWithFileName; 90 function formatDiagnosticToProtocol(diag: Diagnostic, includeFileName: false): protocol.Diagnostic; 91 function formatDiagnosticToProtocol(diag: Diagnostic, includeFileName: boolean): protocol.Diagnostic | protocol.DiagnosticWithFileName { 92 const start = (diag.file && convertToLocation(getLineAndCharacterOfPosition(diag.file, diag.start!)))!; // TODO: GH#18217 93 const end = (diag.file && convertToLocation(getLineAndCharacterOfPosition(diag.file, diag.start! + diag.length!)))!; // TODO: GH#18217 94 const text = flattenDiagnosticMessageText(diag.messageText, "\n"); 95 const { code, source } = diag; 96 const category = diagnosticCategoryName(diag); 97 const common = { 98 start, 99 end, 100 text, 101 code, 102 category, 103 reportsUnnecessary: diag.reportsUnnecessary, 104 reportsDeprecated: diag.reportsDeprecated, 105 source, 106 relatedInformation: map(diag.relatedInformation, formatRelatedInformation), 107 }; 108 return includeFileName 109 ? { ...common, fileName: diag.file && diag.file.fileName } 110 : common; 111 } 112 113 export interface PendingErrorCheck { 114 fileName: NormalizedPath; 115 project: Project; 116 } 117 118 function allEditsBeforePos(edits: readonly TextChange[], pos: number): boolean { 119 return edits.every(edit => textSpanEnd(edit.span) < pos); 120 } 121 122 // CommandNames used to be exposed before TS 2.4 as a namespace 123 // In TS 2.4 we switched to an enum, keep this for backward compatibility 124 // The var assignment ensures that even though CommandTypes are a const enum 125 // we want to ensure the value is maintained in the out since the file is 126 // built using --preseveConstEnum. 127 export type CommandNames = protocol.CommandTypes; 128 export const CommandNames = (<any>protocol).CommandTypes; 129 130 export function formatMessage<T extends protocol.Message>(msg: T, logger: Logger, byteLength: (s: string, encoding: string) => number, newLine: string): string { 131 const verboseLogging = logger.hasLevel(LogLevel.verbose); 132 133 const json = JSON.stringify(msg); 134 if (verboseLogging) { 135 logger.info(`${msg.type}:${indent(json)}`); 136 } 137 138 const len = byteLength(json, "utf8"); 139 return `Content-Length: ${1 + len}\r\n\r\n${json}${newLine}`; 140 } 141 142 /** 143 * Allows to schedule next step in multistep operation 144 */ 145 interface NextStep { 146 immediate(action: () => void): void; 147 delay(ms: number, action: () => void): void; 148 } 149 150 /** 151 * External capabilities used by multistep operation 152 */ 153 interface MultistepOperationHost { 154 getCurrentRequestId(): number; 155 sendRequestCompletedEvent(requestId: number): void; 156 getServerHost(): ServerHost; 157 isCancellationRequested(): boolean; 158 executeWithRequestId(requestId: number, action: () => void): void; 159 logError(error: Error, message: string): void; 160 } 161 162 /** 163 * Represents operation that can schedule its next step to be executed later. 164 * Scheduling is done via instance of NextStep. If on current step subsequent step was not scheduled - operation is assumed to be completed. 165 */ 166 class MultistepOperation implements NextStep { 167 private requestId: number | undefined; 168 private timerHandle: any; 169 private immediateId: number | undefined; 170 171 constructor(private readonly operationHost: MultistepOperationHost) { } 172 173 public startNew(action: (next: NextStep) => void) { 174 this.complete(); 175 this.requestId = this.operationHost.getCurrentRequestId(); 176 this.executeAction(action); 177 } 178 179 private complete() { 180 if (this.requestId !== undefined) { 181 this.operationHost.sendRequestCompletedEvent(this.requestId); 182 this.requestId = undefined; 183 } 184 this.setTimerHandle(undefined); 185 this.setImmediateId(undefined); 186 } 187 188 public immediate(action: () => void) { 189 const requestId = this.requestId!; 190 Debug.assert(requestId === this.operationHost.getCurrentRequestId(), "immediate: incorrect request id"); 191 this.setImmediateId(this.operationHost.getServerHost().setImmediate(() => { 192 this.immediateId = undefined; 193 this.operationHost.executeWithRequestId(requestId, () => this.executeAction(action)); 194 })); 195 } 196 197 public delay(ms: number, action: () => void) { 198 const requestId = this.requestId!; 199 Debug.assert(requestId === this.operationHost.getCurrentRequestId(), "delay: incorrect request id"); 200 this.setTimerHandle(this.operationHost.getServerHost().setTimeout(() => { 201 this.timerHandle = undefined; 202 this.operationHost.executeWithRequestId(requestId, () => this.executeAction(action)); 203 }, ms)); 204 } 205 206 private executeAction(action: (next: NextStep) => void) { 207 let stop = false; 208 try { 209 if (this.operationHost.isCancellationRequested()) { 210 stop = true; 211 tracing?.instant(tracing.Phase.Session, "stepCanceled", { seq: this.requestId, early: true }); 212 } 213 else { 214 tracing?.push(tracing.Phase.Session, "stepAction", { seq: this.requestId }); 215 action(this); 216 tracing?.pop(); 217 } 218 } 219 catch (e) { 220 // Cancellation or an error may have left incomplete events on the tracing stack. 221 tracing?.popAll(); 222 223 stop = true; 224 // ignore cancellation request 225 if (e instanceof OperationCanceledException) { 226 tracing?.instant(tracing.Phase.Session, "stepCanceled", { seq: this.requestId }); 227 } 228 else { 229 tracing?.instant(tracing.Phase.Session, "stepError", { seq: this.requestId, message: (<Error>e).message }); 230 this.operationHost.logError(e, `delayed processing of request ${this.requestId}`); 231 } 232 } 233 if (stop || !this.hasPendingWork()) { 234 this.complete(); 235 } 236 } 237 238 private setTimerHandle(timerHandle: any) { 239 if (this.timerHandle !== undefined) { 240 this.operationHost.getServerHost().clearTimeout(this.timerHandle); 241 } 242 this.timerHandle = timerHandle; 243 } 244 245 private setImmediateId(immediateId: number | undefined) { 246 if (this.immediateId !== undefined) { 247 this.operationHost.getServerHost().clearImmediate(this.immediateId); 248 } 249 this.immediateId = immediateId; 250 } 251 252 private hasPendingWork() { 253 return !!this.timerHandle || !!this.immediateId; 254 } 255 } 256 257 export type Event = <T extends object>(body: T, eventName: string) => void; 258 259 export interface EventSender { 260 event: Event; 261 } 262 263 /** @internal */ 264 export function toEvent(eventName: string, body: object): protocol.Event { 265 return { 266 seq: 0, 267 type: "event", 268 event: eventName, 269 body 270 }; 271 } 272 273 type Projects = readonly Project[] | { 274 readonly projects: readonly Project[]; 275 readonly symLinkedProjects: MultiMap<Path, Project>; 276 }; 277 278 /** 279 * This helper function processes a list of projects and return the concatenated, sortd and deduplicated output of processing each project. 280 */ 281 function combineProjectOutput<T, U>( 282 defaultValue: T, 283 getValue: (path: Path) => T, 284 projects: Projects, 285 action: (project: Project, value: T) => readonly U[] | U | undefined, 286 ): U[] { 287 const outputs = flatMapToMutable(isArray(projects) ? projects : projects.projects, project => action(project, defaultValue)); 288 if (!isArray(projects) && projects.symLinkedProjects) { 289 projects.symLinkedProjects.forEach((projects, path) => { 290 const value = getValue(path); 291 outputs.push(...flatMap(projects, project => action(project, value))); 292 }); 293 } 294 return deduplicate(outputs, equateValues); 295 } 296 297 type CombineOutputResult<T> = { project: Project; result: readonly T[]; }[]; 298 function combineOutputResultContains<T>(outputs: CombineOutputResult<T>, output: T, areEqual: (a: T, b: T) => boolean) { 299 return outputs.some(({ result }) => contains(result, output, areEqual)); 300 } 301 function addToCombineOutputResult<T>(outputs: CombineOutputResult<T>, project: Project, result: readonly T[]) { 302 if (result.length) outputs.push({ project, result }); 303 } 304 305 function combineProjectOutputFromEveryProject<T>(projectService: ProjectService, action: (project: Project) => readonly T[], areEqual: (a: T, b: T) => boolean): CombineOutputResult<T> { 306 const outputs: CombineOutputResult<T> = []; 307 projectService.loadAncestorProjectTree(); 308 projectService.forEachEnabledProject(project => { 309 const theseOutputs = action(project); 310 addToCombineOutputResult(outputs, project, filter(theseOutputs, output => !combineOutputResultContains(outputs, output, areEqual))); 311 }); 312 return outputs; 313 } 314 315 function flattenCombineOutputResult<T>(outputs: CombineOutputResult<T>): readonly T[] { 316 return flatMap(outputs, ({ result }) => result); 317 } 318 319 function combineProjectOutputWhileOpeningReferencedProjects<T>( 320 projects: Projects, 321 defaultProject: Project, 322 action: (project: Project) => readonly T[], 323 getLocation: (t: T) => DocumentPosition, 324 resultsEqual: (a: T, b: T) => boolean, 325 ): CombineOutputResult<T> { 326 const outputs: CombineOutputResult<T> = []; 327 combineProjectOutputWorker( 328 projects, 329 defaultProject, 330 /*initialLocation*/ undefined, 331 (project, _, tryAddToTodo) => { 332 const theseOutputs = action(project); 333 addToCombineOutputResult(outputs, project, filter(theseOutputs, output => !combineOutputResultContains(outputs, output, resultsEqual) && !tryAddToTodo(project, getLocation(output)))); 334 }, 335 ); 336 return outputs; 337 } 338 339 function combineProjectOutputForRenameLocations( 340 projects: Projects, 341 defaultProject: Project, 342 initialLocation: DocumentPosition, 343 findInStrings: boolean, 344 findInComments: boolean, 345 hostPreferences: UserPreferences 346 ): readonly RenameLocation[] { 347 const outputs: RenameLocation[] = []; 348 combineProjectOutputWorker( 349 projects, 350 defaultProject, 351 initialLocation, 352 (project, location, tryAddToTodo) => { 353 for (const output of project.getLanguageService().findRenameLocations(location.fileName, location.pos, findInStrings, findInComments, hostPreferences.providePrefixAndSuffixTextForRename) || emptyArray) { 354 if (!contains(outputs, output, documentSpansEqual) && !tryAddToTodo(project, documentSpanLocation(output))) { 355 outputs.push(output); 356 } 357 } 358 }, 359 ); 360 361 return outputs; 362 } 363 364 function getDefinitionLocation(defaultProject: Project, initialLocation: DocumentPosition): DocumentPosition | undefined { 365 const infos = defaultProject.getLanguageService().getDefinitionAtPosition(initialLocation.fileName, initialLocation.pos); 366 const info = infos && firstOrUndefined(infos); 367 return info && !info.isLocal ? { fileName: info.fileName, pos: info.textSpan.start } : undefined; 368 } 369 370 function combineProjectOutputForReferences( 371 projects: Projects, 372 defaultProject: Project, 373 initialLocation: DocumentPosition 374 ): readonly ReferencedSymbol[] { 375 const outputs: ReferencedSymbol[] = []; 376 377 combineProjectOutputWorker( 378 projects, 379 defaultProject, 380 initialLocation, 381 (project, location, getMappedLocation) => { 382 for (const outputReferencedSymbol of project.getLanguageService().findReferences(location.fileName, location.pos) || emptyArray) { 383 const mappedDefinitionFile = getMappedLocation(project, documentSpanLocation(outputReferencedSymbol.definition)); 384 const definition: ReferencedSymbolDefinitionInfo = mappedDefinitionFile === undefined ? 385 outputReferencedSymbol.definition : 386 { 387 ...outputReferencedSymbol.definition, 388 textSpan: createTextSpan(mappedDefinitionFile.pos, outputReferencedSymbol.definition.textSpan.length), 389 fileName: mappedDefinitionFile.fileName, 390 contextSpan: getMappedContextSpan(outputReferencedSymbol.definition, project) 391 }; 392 393 let symbolToAddTo = find(outputs, o => documentSpansEqual(o.definition, definition)); 394 if (!symbolToAddTo) { 395 symbolToAddTo = { definition, references: [] }; 396 outputs.push(symbolToAddTo); 397 } 398 399 for (const ref of outputReferencedSymbol.references) { 400 // If it's in a mapped file, that is added to the todo list by `getMappedLocation`. 401 if (!contains(symbolToAddTo.references, ref, documentSpansEqual) && !getMappedLocation(project, documentSpanLocation(ref))) { 402 symbolToAddTo.references.push(ref); 403 } 404 } 405 } 406 }, 407 ); 408 409 return outputs.filter(o => o.references.length !== 0); 410 } 411 412 function combineProjectOutputForFileReferences( 413 projects: Projects, 414 defaultProject: Project, 415 fileName: string 416 ): readonly ReferenceEntry[] { 417 const outputs: ReferenceEntry[] = []; 418 419 combineProjectOutputWorker( 420 projects, 421 defaultProject, 422 /*initialLocation*/ undefined, 423 project => { 424 for (const referenceEntry of project.getLanguageService().getFileReferences(fileName) || emptyArray) { 425 if (!contains(outputs, referenceEntry, documentSpansEqual)) { 426 outputs.push(referenceEntry); 427 } 428 } 429 }, 430 ); 431 432 return outputs; 433 } 434 435 interface ProjectAndLocation<TLocation extends DocumentPosition | undefined> { 436 readonly project: Project; 437 readonly location: TLocation; 438 } 439 440 function forEachProjectInProjects(projects: Projects, path: string | undefined, cb: (project: Project, path: string | undefined) => void): void { 441 for (const project of isArray(projects) ? projects : projects.projects) { 442 cb(project, path); 443 } 444 if (!isArray(projects) && projects.symLinkedProjects) { 445 projects.symLinkedProjects.forEach((symlinkedProjects, symlinkedPath) => { 446 for (const project of symlinkedProjects) { 447 cb(project, symlinkedPath); 448 } 449 }); 450 } 451 } 452 453 type CombineProjectOutputCallback<TLocation extends DocumentPosition | undefined> = ( 454 project: Project, 455 location: TLocation, 456 getMappedLocation: (project: Project, location: DocumentPosition) => DocumentPosition | undefined, 457 ) => void; 458 459 function combineProjectOutputWorker<TLocation extends DocumentPosition | undefined>( 460 projects: Projects, 461 defaultProject: Project, 462 initialLocation: TLocation, 463 cb: CombineProjectOutputCallback<TLocation> 464 ): void { 465 const projectService = defaultProject.projectService; 466 let toDo: ProjectAndLocation<TLocation>[] | undefined; 467 const seenProjects = new Set<string>(); 468 forEachProjectInProjects(projects, initialLocation && initialLocation.fileName, (project, path) => { 469 // TLocation should be either `DocumentPosition` or `undefined`. Since `initialLocation` is `TLocation` this cast should be valid. 470 const location = (initialLocation ? { fileName: path, pos: initialLocation.pos } : undefined) as TLocation; 471 toDo = callbackProjectAndLocation(project, location, projectService, toDo, seenProjects, cb); 472 }); 473 474 // After initial references are collected, go over every other project and see if it has a reference for the symbol definition. 475 if (initialLocation) { 476 const defaultDefinition = getDefinitionLocation(defaultProject, initialLocation!); 477 if (defaultDefinition) { 478 const getGeneratedDefinition = memoize(() => defaultProject.isSourceOfProjectReferenceRedirect(defaultDefinition.fileName) ? 479 defaultDefinition : 480 defaultProject.getLanguageService().getSourceMapper().tryGetGeneratedPosition(defaultDefinition)); 481 const getSourceDefinition = memoize(() => defaultProject.isSourceOfProjectReferenceRedirect(defaultDefinition.fileName) ? 482 defaultDefinition : 483 defaultProject.getLanguageService().getSourceMapper().tryGetSourcePosition(defaultDefinition)); 484 projectService.loadAncestorProjectTree(seenProjects); 485 projectService.forEachEnabledProject(project => { 486 if (!addToSeen(seenProjects, project)) return; 487 const definition = mapDefinitionInProject(defaultDefinition, project, getGeneratedDefinition, getSourceDefinition); 488 if (definition) { 489 toDo = callbackProjectAndLocation<TLocation>(project, definition as TLocation, projectService, toDo, seenProjects, cb); 490 } 491 }); 492 } 493 } 494 495 while (toDo && toDo.length) { 496 const next = toDo.pop(); 497 Debug.assertIsDefined(next); 498 toDo = callbackProjectAndLocation(next.project, next.location, projectService, toDo, seenProjects, cb); 499 } 500 } 501 502 function mapDefinitionInProject( 503 definition: DocumentPosition, 504 project: Project, 505 getGeneratedDefinition: () => DocumentPosition | undefined, 506 getSourceDefinition: () => DocumentPosition | undefined 507 ): DocumentPosition | undefined { 508 // If the definition is actually from the project, definition is correct as is 509 if (project.containsFile(toNormalizedPath(definition.fileName)) && 510 !isLocationProjectReferenceRedirect(project, definition)) { 511 return definition; 512 } 513 const generatedDefinition = getGeneratedDefinition(); 514 if (generatedDefinition && project.containsFile(toNormalizedPath(generatedDefinition.fileName))) return generatedDefinition; 515 const sourceDefinition = getSourceDefinition(); 516 return sourceDefinition && project.containsFile(toNormalizedPath(sourceDefinition.fileName)) ? sourceDefinition : undefined; 517 } 518 519 function isLocationProjectReferenceRedirect(project: Project, location: DocumentPosition | undefined) { 520 if (!location) return false; 521 const program = project.getLanguageService().getProgram(); 522 if (!program) return false; 523 const sourceFile = program.getSourceFile(location.fileName); 524 525 // It is possible that location is attached to project but 526 // the program actually includes its redirect instead. 527 // This happens when rootFile in project is one of the file from referenced project 528 // Thus root is attached but program doesnt have the actual .ts file but .d.ts 529 // If this is not the file we were actually looking, return rest of the toDo 530 return !!sourceFile && 531 sourceFile.resolvedPath !== sourceFile.path && 532 sourceFile.resolvedPath !== project.toPath(location.fileName); 533 } 534 535 function callbackProjectAndLocation<TLocation extends DocumentPosition | undefined>( 536 project: Project, 537 location: TLocation, 538 projectService: ProjectService, 539 toDo: ProjectAndLocation<TLocation>[] | undefined, 540 seenProjects: Set<string>, 541 cb: CombineProjectOutputCallback<TLocation>, 542 ): ProjectAndLocation<TLocation>[] | undefined { 543 if (project.getCancellationToken().isCancellationRequested()) return undefined; // Skip rest of toDo if cancelled 544 // If this is not the file we were actually looking, return rest of the toDo 545 if (isLocationProjectReferenceRedirect(project, location)) return toDo; 546 cb(project, location, (innerProject, location) => { 547 addToSeen(seenProjects, project); 548 const originalLocation = projectService.getOriginalLocationEnsuringConfiguredProject(innerProject, location); 549 if (!originalLocation) return undefined; 550 551 const originalScriptInfo = projectService.getScriptInfo(originalLocation.fileName)!; 552 toDo = toDo || []; 553 554 for (const project of originalScriptInfo.containingProjects) { 555 addToTodo(project, originalLocation as TLocation, toDo, seenProjects); 556 } 557 const symlinkedProjectsMap = projectService.getSymlinkedProjects(originalScriptInfo); 558 if (symlinkedProjectsMap) { 559 symlinkedProjectsMap.forEach((symlinkedProjects, symlinkedPath) => { 560 for (const symlinkedProject of symlinkedProjects) { 561 addToTodo(symlinkedProject, { fileName: symlinkedPath as string, pos: originalLocation.pos } as TLocation, toDo!, seenProjects); 562 } 563 }); 564 } 565 return originalLocation === location ? undefined : originalLocation; 566 }); 567 return toDo; 568 } 569 570 function addToTodo<TLocation extends DocumentPosition | undefined>(project: Project, location: TLocation, toDo: Push<ProjectAndLocation<TLocation>>, seenProjects: Set<string>): void { 571 if (!project.isOrphan() && addToSeen(seenProjects, project)) toDo.push({ project, location }); 572 } 573 574 function addToSeen(seenProjects: Set<string>, project: Project) { 575 return tryAddToSet(seenProjects, getProjectKey(project)); 576 } 577 578 function getProjectKey(project: Project) { 579 return isConfiguredProject(project) ? project.canonicalConfigFilePath : project.getProjectName(); 580 } 581 582 function documentSpanLocation({ fileName, textSpan }: DocumentSpan): DocumentPosition { 583 return { fileName, pos: textSpan.start }; 584 } 585 586 function getMappedLocation(location: DocumentPosition, project: Project): DocumentPosition | undefined { 587 const mapsTo = project.getSourceMapper().tryGetSourcePosition(location); 588 return mapsTo && project.projectService.fileExists(toNormalizedPath(mapsTo.fileName)) ? mapsTo : undefined; 589 } 590 591 function getMappedDocumentSpan(documentSpan: DocumentSpan, project: Project): DocumentSpan | undefined { 592 const newPosition = getMappedLocation(documentSpanLocation(documentSpan), project); 593 if (!newPosition) return undefined; 594 return { 595 fileName: newPosition.fileName, 596 textSpan: { 597 start: newPosition.pos, 598 length: documentSpan.textSpan.length 599 }, 600 originalFileName: documentSpan.fileName, 601 originalTextSpan: documentSpan.textSpan, 602 contextSpan: getMappedContextSpan(documentSpan, project), 603 originalContextSpan: documentSpan.contextSpan 604 }; 605 } 606 607 function getMappedContextSpan(documentSpan: DocumentSpan, project: Project): TextSpan | undefined { 608 const contextSpanStart = documentSpan.contextSpan && getMappedLocation( 609 { fileName: documentSpan.fileName, pos: documentSpan.contextSpan.start }, 610 project 611 ); 612 const contextSpanEnd = documentSpan.contextSpan && getMappedLocation( 613 { fileName: documentSpan.fileName, pos: documentSpan.contextSpan.start + documentSpan.contextSpan.length }, 614 project 615 ); 616 return contextSpanStart && contextSpanEnd ? 617 { start: contextSpanStart.pos, length: contextSpanEnd.pos - contextSpanStart.pos } : 618 undefined; 619 } 620 621 const invalidPartialSemanticModeCommands: readonly CommandNames[] = [ 622 CommandNames.OpenExternalProject, 623 CommandNames.OpenExternalProjects, 624 CommandNames.CloseExternalProject, 625 CommandNames.SynchronizeProjectList, 626 CommandNames.EmitOutput, 627 CommandNames.CompileOnSaveAffectedFileList, 628 CommandNames.CompileOnSaveEmitFile, 629 CommandNames.CompilerOptionsDiagnosticsFull, 630 CommandNames.EncodedSemanticClassificationsFull, 631 CommandNames.SemanticDiagnosticsSync, 632 CommandNames.SyntacticDiagnosticsSync, 633 CommandNames.SuggestionDiagnosticsSync, 634 CommandNames.Geterr, 635 CommandNames.GeterrForProject, 636 CommandNames.Reload, 637 CommandNames.ReloadProjects, 638 CommandNames.GetCodeFixes, 639 CommandNames.GetCodeFixesFull, 640 CommandNames.GetCombinedCodeFix, 641 CommandNames.GetCombinedCodeFixFull, 642 CommandNames.ApplyCodeActionCommand, 643 CommandNames.GetSupportedCodeFixes, 644 CommandNames.GetApplicableRefactors, 645 CommandNames.GetEditsForRefactor, 646 CommandNames.GetEditsForRefactorFull, 647 CommandNames.OrganizeImports, 648 CommandNames.OrganizeImportsFull, 649 CommandNames.GetEditsForFileRename, 650 CommandNames.GetEditsForFileRenameFull, 651 CommandNames.ConfigurePlugin, 652 CommandNames.PrepareCallHierarchy, 653 CommandNames.ProvideCallHierarchyIncomingCalls, 654 CommandNames.ProvideCallHierarchyOutgoingCalls, 655 ]; 656 657 const invalidSyntacticModeCommands: readonly CommandNames[] = [ 658 ...invalidPartialSemanticModeCommands, 659 CommandNames.Definition, 660 CommandNames.DefinitionFull, 661 CommandNames.DefinitionAndBoundSpan, 662 CommandNames.DefinitionAndBoundSpanFull, 663 CommandNames.TypeDefinition, 664 CommandNames.Implementation, 665 CommandNames.ImplementationFull, 666 CommandNames.References, 667 CommandNames.ReferencesFull, 668 CommandNames.Rename, 669 CommandNames.RenameLocationsFull, 670 CommandNames.RenameInfoFull, 671 CommandNames.Quickinfo, 672 CommandNames.QuickinfoFull, 673 CommandNames.CompletionInfo, 674 CommandNames.Completions, 675 CommandNames.CompletionsFull, 676 CommandNames.CompletionDetails, 677 CommandNames.CompletionDetailsFull, 678 CommandNames.SignatureHelp, 679 CommandNames.SignatureHelpFull, 680 CommandNames.Navto, 681 CommandNames.NavtoFull, 682 CommandNames.Occurrences, 683 CommandNames.DocumentHighlights, 684 CommandNames.DocumentHighlightsFull, 685 ]; 686 687 export interface SessionOptions { 688 host: ServerHost; 689 cancellationToken: ServerCancellationToken; 690 useSingleInferredProject: boolean; 691 useInferredProjectPerProjectRoot: boolean; 692 typingsInstaller: ITypingsInstaller; 693 byteLength: (buf: string, encoding?: string) => number; 694 hrtime: (start?: number[]) => number[]; 695 logger: Logger; 696 /** 697 * If falsy, all events are suppressed. 698 */ 699 canUseEvents: boolean; 700 eventHandler?: ProjectServiceEventHandler; 701 /** Has no effect if eventHandler is also specified. */ 702 suppressDiagnosticEvents?: boolean; 703 /** @deprecated use serverMode instead */ 704 syntaxOnly?: boolean; 705 serverMode?: LanguageServiceMode; 706 throttleWaitMilliseconds?: number; 707 noGetErrOnBackgroundUpdate?: boolean; 708 709 globalPlugins?: readonly string[]; 710 pluginProbeLocations?: readonly string[]; 711 allowLocalPluginLoads?: boolean; 712 typesMapLocation?: string; 713 } 714 715 export class Session<TMessage = string> implements EventSender { 716 private readonly gcTimer: GcTimer; 717 protected projectService: ProjectService; 718 private changeSeq = 0; 719 720 private performanceData: protocol.PerformanceData | undefined; 721 722 private currentRequestId!: number; 723 private errorCheck: MultistepOperation; 724 725 protected host: ServerHost; 726 private readonly cancellationToken: ServerCancellationToken; 727 protected readonly typingsInstaller: ITypingsInstaller; 728 protected byteLength: (buf: string, encoding?: string) => number; 729 private hrtime: (start?: number[]) => number[]; 730 protected logger: Logger; 731 732 protected canUseEvents: boolean; 733 private suppressDiagnosticEvents?: boolean; 734 private eventHandler: ProjectServiceEventHandler | undefined; 735 private readonly noGetErrOnBackgroundUpdate?: boolean; 736 737 constructor(opts: SessionOptions) { 738 this.host = opts.host; 739 this.cancellationToken = opts.cancellationToken; 740 this.typingsInstaller = opts.typingsInstaller; 741 this.byteLength = opts.byteLength; 742 this.hrtime = opts.hrtime; 743 this.logger = opts.logger; 744 this.canUseEvents = opts.canUseEvents; 745 this.suppressDiagnosticEvents = opts.suppressDiagnosticEvents; 746 this.noGetErrOnBackgroundUpdate = opts.noGetErrOnBackgroundUpdate; 747 748 const { throttleWaitMilliseconds } = opts; 749 750 this.eventHandler = this.canUseEvents 751 ? opts.eventHandler || (event => this.defaultEventHandler(event)) 752 : undefined; 753 const multistepOperationHost: MultistepOperationHost = { 754 executeWithRequestId: (requestId, action) => this.executeWithRequestId(requestId, action), 755 getCurrentRequestId: () => this.currentRequestId, 756 getServerHost: () => this.host, 757 logError: (err, cmd) => this.logError(err, cmd), 758 sendRequestCompletedEvent: requestId => this.sendRequestCompletedEvent(requestId), 759 isCancellationRequested: () => this.cancellationToken.isCancellationRequested() 760 }; 761 this.errorCheck = new MultistepOperation(multistepOperationHost); 762 const settings: ProjectServiceOptions = { 763 host: this.host, 764 logger: this.logger, 765 cancellationToken: this.cancellationToken, 766 useSingleInferredProject: opts.useSingleInferredProject, 767 useInferredProjectPerProjectRoot: opts.useInferredProjectPerProjectRoot, 768 typingsInstaller: this.typingsInstaller, 769 throttleWaitMilliseconds, 770 eventHandler: this.eventHandler, 771 suppressDiagnosticEvents: this.suppressDiagnosticEvents, 772 globalPlugins: opts.globalPlugins, 773 pluginProbeLocations: opts.pluginProbeLocations, 774 allowLocalPluginLoads: opts.allowLocalPluginLoads, 775 typesMapLocation: opts.typesMapLocation, 776 syntaxOnly: opts.syntaxOnly, 777 serverMode: opts.serverMode, 778 }; 779 this.projectService = new ProjectService(settings); 780 this.projectService.setPerformanceEventHandler(this.performanceEventHandler.bind(this)); 781 this.gcTimer = new GcTimer(this.host, /*delay*/ 7000, this.logger); 782 783 // Make sure to setup handlers to throw error for not allowed commands on syntax server 784 switch (this.projectService.serverMode) { 785 case LanguageServiceMode.Semantic: 786 break; 787 case LanguageServiceMode.PartialSemantic: 788 invalidPartialSemanticModeCommands.forEach(commandName => 789 this.handlers.set(commandName, request => { 790 throw new Error(`Request: ${request.command} not allowed in LanguageServiceMode.PartialSemantic`); 791 }) 792 ); 793 break; 794 case LanguageServiceMode.Syntactic: 795 invalidSyntacticModeCommands.forEach(commandName => 796 this.handlers.set(commandName, request => { 797 throw new Error(`Request: ${request.command} not allowed in LanguageServiceMode.Syntactic`); 798 }) 799 ); 800 break; 801 default: 802 Debug.assertNever(this.projectService.serverMode); 803 } 804 } 805 806 private sendRequestCompletedEvent(requestId: number): void { 807 this.event<protocol.RequestCompletedEventBody>({ request_seq: requestId }, "requestCompleted"); 808 } 809 810 private addPerformanceData(key: keyof protocol.PerformanceData, value: number) { 811 if (!this.performanceData) { 812 this.performanceData = {}; 813 } 814 this.performanceData[key] = (this.performanceData[key] ?? 0) + value; 815 } 816 817 private performanceEventHandler(event: PerformanceEvent) { 818 switch (event.kind) { 819 case "UpdateGraph": 820 this.addPerformanceData("updateGraphDurationMs", event.durationMs); 821 break; 822 case "CreatePackageJsonAutoImportProvider": 823 this.addPerformanceData("createAutoImportProviderProgramDurationMs", event.durationMs); 824 break; 825 } 826 } 827 828 private defaultEventHandler(event: ProjectServiceEvent) { 829 switch (event.eventName) { 830 case ProjectsUpdatedInBackgroundEvent: 831 const { openFiles } = event.data; 832 this.projectsUpdatedInBackgroundEvent(openFiles); 833 break; 834 case ProjectLoadingStartEvent: 835 const { project, reason } = event.data; 836 this.event<protocol.ProjectLoadingStartEventBody>( 837 { projectName: project.getProjectName(), reason }, 838 ProjectLoadingStartEvent); 839 break; 840 case ProjectLoadingFinishEvent: 841 const { project: finishProject } = event.data; 842 this.event<protocol.ProjectLoadingFinishEventBody>({ projectName: finishProject.getProjectName() }, ProjectLoadingFinishEvent); 843 break; 844 case LargeFileReferencedEvent: 845 const { file, fileSize, maxFileSize } = event.data; 846 this.event<protocol.LargeFileReferencedEventBody>({ file, fileSize, maxFileSize }, LargeFileReferencedEvent); 847 break; 848 case ConfigFileDiagEvent: 849 const { triggerFile, configFileName: configFile, diagnostics } = event.data; 850 const bakedDiags = map(diagnostics, diagnostic => formatDiagnosticToProtocol(diagnostic, /*includeFileName*/ true)); 851 this.event<protocol.ConfigFileDiagnosticEventBody>({ 852 triggerFile, 853 configFile, 854 diagnostics: bakedDiags 855 }, ConfigFileDiagEvent); 856 break; 857 case ProjectLanguageServiceStateEvent: { 858 const eventName: protocol.ProjectLanguageServiceStateEventName = ProjectLanguageServiceStateEvent; 859 this.event<protocol.ProjectLanguageServiceStateEventBody>({ 860 projectName: event.data.project.getProjectName(), 861 languageServiceEnabled: event.data.languageServiceEnabled 862 }, eventName); 863 break; 864 } 865 case ProjectInfoTelemetryEvent: { 866 const eventName: protocol.TelemetryEventName = "telemetry"; 867 this.event<protocol.TelemetryEventBody>({ 868 telemetryEventName: event.eventName, 869 payload: event.data, 870 }, eventName); 871 break; 872 } 873 } 874 } 875 876 private projectsUpdatedInBackgroundEvent(openFiles: string[]): void { 877 this.projectService.logger.info(`got projects updated in background, updating diagnostics for ${openFiles}`); 878 if (openFiles.length) { 879 if (!this.suppressDiagnosticEvents && !this.noGetErrOnBackgroundUpdate) { 880 // For now only queue error checking for open files. We can change this to include non open files as well 881 this.errorCheck.startNew(next => this.updateErrorCheck(next, openFiles, 100, /*requireOpen*/ true)); 882 } 883 884 // Send project changed event 885 this.event<protocol.ProjectsUpdatedInBackgroundEventBody>({ 886 openFiles 887 }, ProjectsUpdatedInBackgroundEvent); 888 } 889 } 890 891 public logError(err: Error, cmd: string): void { 892 this.logErrorWorker(err, cmd); 893 } 894 895 private logErrorWorker(err: Error & PossibleProgramFileInfo, cmd: string, fileRequest?: protocol.FileRequestArgs): void { 896 let msg = "Exception on executing command " + cmd; 897 if (err.message) { 898 msg += ":\n" + indent(err.message); 899 if ((<StackTraceError>err).stack) { 900 msg += "\n" + indent((<StackTraceError>err).stack!); 901 } 902 } 903 904 if (this.logger.hasLevel(LogLevel.verbose)) { 905 if (fileRequest) { 906 try { 907 const { file, project } = this.getFileAndProject(fileRequest); 908 const scriptInfo = project.getScriptInfoForNormalizedPath(file); 909 if (scriptInfo) { 910 const text = getSnapshotText(scriptInfo.getSnapshot()); 911 msg += `\n\nFile text of ${fileRequest.file}:${indent(text)}\n`; 912 } 913 } 914 catch { } // eslint-disable-line no-empty 915 } 916 917 918 if (err.ProgramFiles) { 919 msg += `\n\nProgram files: ${JSON.stringify(err.ProgramFiles)}\n`; 920 msg += `\n\nProjects::\n`; 921 let counter = 0; 922 const addProjectInfo = (project: Project) => { 923 msg += `\nProject '${project.projectName}' (${ProjectKind[project.projectKind]}) ${counter}\n`; 924 msg += project.filesToString(/*writeProjectFileNames*/ true); 925 msg += "\n-----------------------------------------------\n"; 926 counter++; 927 }; 928 this.projectService.externalProjects.forEach(addProjectInfo); 929 this.projectService.configuredProjects.forEach(addProjectInfo); 930 this.projectService.inferredProjects.forEach(addProjectInfo); 931 } 932 } 933 934 this.logger.msg(msg, Msg.Err); 935 } 936 937 public send(msg: protocol.Message) { 938 if (msg.type === "event" && !this.canUseEvents) { 939 if (this.logger.hasLevel(LogLevel.verbose)) { 940 this.logger.info(`Session does not support events: ignored event: ${JSON.stringify(msg)}`); 941 } 942 return; 943 } 944 const msgText = formatMessage(msg, this.logger, this.byteLength, this.host.newLine); 945 perfLogger.logEvent(`Response message size: ${msgText.length}`); 946 this.host.write(msgText); 947 } 948 949 public event<T extends object>(body: T, eventName: string): void { 950 tracing?.instant(tracing.Phase.Session, "event", { eventName }); 951 this.send(toEvent(eventName, body)); 952 } 953 954 // For backwards-compatibility only. 955 /** @deprecated */ 956 public output(info: any, cmdName: string, reqSeq?: number, errorMsg?: string): void { 957 this.doOutput(info, cmdName, reqSeq!, /*success*/ !errorMsg, errorMsg); // TODO: GH#18217 958 } 959 960 private doOutput(info: {} | undefined, cmdName: string, reqSeq: number, success: boolean, message?: string): void { 961 const res: protocol.Response = { 962 seq: 0, 963 type: "response", 964 command: cmdName, 965 request_seq: reqSeq, 966 success, 967 performanceData: this.performanceData 968 }; 969 970 if (success) { 971 let metadata: unknown; 972 if (isArray(info)) { 973 res.body = info; 974 metadata = (info as WithMetadata<readonly any[]>).metadata; 975 delete (info as WithMetadata<readonly any[]>).metadata; 976 } 977 else if (typeof info === "object") { 978 if ((info as WithMetadata<{}>).metadata) { 979 const { metadata: infoMetadata, ...body } = (info as WithMetadata<{}>); 980 res.body = body; 981 metadata = infoMetadata; 982 } 983 else { 984 res.body = info; 985 } 986 } 987 else { 988 res.body = info; 989 } 990 if (metadata) res.metadata = metadata; 991 } 992 else { 993 Debug.assert(info === undefined); 994 } 995 if (message) { 996 res.message = message; 997 } 998 this.send(res); 999 } 1000 1001 private semanticCheck(file: NormalizedPath, project: Project) { 1002 const diags = isDeclarationFileInJSOnlyNonConfiguredProject(project, file) 1003 ? emptyArray 1004 : project.getLanguageService().getSemanticDiagnostics(file).filter(d => !!d.file); 1005 this.sendDiagnosticsEvent(file, project, diags, "semanticDiag"); 1006 } 1007 1008 private syntacticCheck(file: NormalizedPath, project: Project) { 1009 this.sendDiagnosticsEvent(file, project, project.getLanguageService().getSyntacticDiagnostics(file), "syntaxDiag"); 1010 } 1011 1012 private suggestionCheck(file: NormalizedPath, project: Project) { 1013 this.sendDiagnosticsEvent(file, project, project.getLanguageService().getSuggestionDiagnostics(file), "suggestionDiag"); 1014 } 1015 1016 private sendDiagnosticsEvent(file: NormalizedPath, project: Project, diagnostics: readonly Diagnostic[], kind: protocol.DiagnosticEventKind): void { 1017 try { 1018 this.event<protocol.DiagnosticEventBody>({ file, diagnostics: diagnostics.map(diag => formatDiag(file, project, diag)) }, kind); 1019 } 1020 catch (err) { 1021 this.logError(err, kind); 1022 } 1023 } 1024 1025 /** It is the caller's responsibility to verify that `!this.suppressDiagnosticEvents`. */ 1026 private updateErrorCheck(next: NextStep, checkList: readonly string[] | readonly PendingErrorCheck[], ms: number, requireOpen = true) { 1027 Debug.assert(!this.suppressDiagnosticEvents); // Caller's responsibility 1028 1029 const seq = this.changeSeq; 1030 const followMs = Math.min(ms, 200); 1031 1032 let index = 0; 1033 const goNext = () => { 1034 index++; 1035 if (checkList.length > index) { 1036 next.delay(followMs, checkOne); 1037 } 1038 }; 1039 const checkOne = () => { 1040 if (this.changeSeq !== seq) { 1041 return; 1042 } 1043 1044 let item: string | PendingErrorCheck | undefined = checkList[index]; 1045 if (isString(item)) { 1046 // Find out project for the file name 1047 item = this.toPendingErrorCheck(item); 1048 if (!item) { 1049 // Ignore file if there is no project for the file 1050 goNext(); 1051 return; 1052 } 1053 } 1054 1055 const { fileName, project } = item; 1056 1057 // Ensure the project is upto date before checking if this file is present in the project 1058 updateProjectIfDirty(project); 1059 if (!project.containsFile(fileName, requireOpen)) { 1060 return; 1061 } 1062 1063 this.syntacticCheck(fileName, project); 1064 if (this.changeSeq !== seq) { 1065 return; 1066 } 1067 1068 next.immediate(() => { 1069 this.semanticCheck(fileName, project); 1070 if (this.changeSeq !== seq) { 1071 return; 1072 } 1073 1074 if (this.getPreferences(fileName).disableSuggestions) { 1075 goNext(); 1076 } 1077 else { 1078 next.immediate(() => { 1079 this.suggestionCheck(fileName, project); 1080 goNext(); 1081 }); 1082 } 1083 }); 1084 }; 1085 1086 if (checkList.length > index && this.changeSeq === seq) { 1087 next.delay(ms, checkOne); 1088 } 1089 } 1090 1091 private cleanProjects(caption: string, projects: Project[]) { 1092 if (!projects) { 1093 return; 1094 } 1095 this.logger.info(`cleaning ${caption}`); 1096 for (const p of projects) { 1097 p.getLanguageService(/*ensureSynchronized*/ false).cleanupSemanticCache(); 1098 } 1099 } 1100 1101 private cleanup() { 1102 this.cleanProjects("inferred projects", this.projectService.inferredProjects); 1103 this.cleanProjects("configured projects", arrayFrom(this.projectService.configuredProjects.values())); 1104 this.cleanProjects("external projects", this.projectService.externalProjects); 1105 if (this.host.gc) { 1106 this.logger.info(`host.gc()`); 1107 this.host.gc(); 1108 } 1109 } 1110 1111 private getEncodedSyntacticClassifications(args: protocol.EncodedSyntacticClassificationsRequestArgs) { 1112 const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); 1113 return languageService.getEncodedSyntacticClassifications(file, args); 1114 } 1115 1116 private getEncodedSemanticClassifications(args: protocol.EncodedSemanticClassificationsRequestArgs) { 1117 const { file, project } = this.getFileAndProject(args); 1118 const format = args.format === "2020" ? SemanticClassificationFormat.TwentyTwenty : SemanticClassificationFormat.Original; 1119 return project.getLanguageService().getEncodedSemanticClassifications(file, args, format); 1120 } 1121 1122 private getProject(projectFileName: string | undefined): Project | undefined { 1123 return projectFileName === undefined ? undefined : this.projectService.findProject(projectFileName); 1124 } 1125 1126 private getConfigFileAndProject(args: protocol.FileRequestArgs): { configFile: NormalizedPath | undefined, project: Project | undefined } { 1127 const project = this.getProject(args.projectFileName); 1128 const file = toNormalizedPath(args.file); 1129 1130 return { 1131 configFile: project && project.hasConfigFile(file) ? file : undefined, 1132 project 1133 }; 1134 } 1135 1136 private getConfigFileDiagnostics(configFile: NormalizedPath, project: Project, includeLinePosition: boolean) { 1137 const projectErrors = project.getAllProjectErrors(); 1138 const optionsErrors = project.getLanguageService().getCompilerOptionsDiagnostics(); 1139 const diagnosticsForConfigFile = filter( 1140 concatenate(projectErrors, optionsErrors), 1141 diagnostic => !!diagnostic.file && diagnostic.file.fileName === configFile 1142 ); 1143 return includeLinePosition ? 1144 this.convertToDiagnosticsWithLinePositionFromDiagnosticFile(diagnosticsForConfigFile) : 1145 map( 1146 diagnosticsForConfigFile, 1147 diagnostic => formatDiagnosticToProtocol(diagnostic, /*includeFileName*/ false) 1148 ); 1149 } 1150 1151 private convertToDiagnosticsWithLinePositionFromDiagnosticFile(diagnostics: readonly Diagnostic[]): protocol.DiagnosticWithLinePosition[] { 1152 return diagnostics.map<protocol.DiagnosticWithLinePosition>(d => ({ 1153 message: flattenDiagnosticMessageText(d.messageText, this.host.newLine), 1154 start: d.start!, // TODO: GH#18217 1155 length: d.length!, // TODO: GH#18217 1156 category: diagnosticCategoryName(d), 1157 code: d.code, 1158 source: d.source, 1159 startLocation: (d.file && convertToLocation(getLineAndCharacterOfPosition(d.file, d.start!)))!, // TODO: GH#18217 1160 endLocation: (d.file && convertToLocation(getLineAndCharacterOfPosition(d.file, d.start! + d.length!)))!, // TODO: GH#18217 1161 reportsUnnecessary: d.reportsUnnecessary, 1162 reportsDeprecated: d.reportsDeprecated, 1163 relatedInformation: map(d.relatedInformation, formatRelatedInformation) 1164 })); 1165 } 1166 1167 private getCompilerOptionsDiagnostics(args: protocol.CompilerOptionsDiagnosticsRequestArgs) { 1168 const project = this.getProject(args.projectFileName)!; 1169 // Get diagnostics that dont have associated file with them 1170 // The diagnostics which have file would be in config file and 1171 // would be reported as part of configFileDiagnostics 1172 return this.convertToDiagnosticsWithLinePosition( 1173 filter( 1174 project.getLanguageService().getCompilerOptionsDiagnostics(), 1175 diagnostic => !diagnostic.file 1176 ), 1177 /*scriptInfo*/ undefined 1178 ); 1179 } 1180 1181 private convertToDiagnosticsWithLinePosition(diagnostics: readonly Diagnostic[], scriptInfo: ScriptInfo | undefined): protocol.DiagnosticWithLinePosition[] { 1182 return diagnostics.map(d => <protocol.DiagnosticWithLinePosition>{ 1183 message: flattenDiagnosticMessageText(d.messageText, this.host.newLine), 1184 start: d.start, 1185 length: d.length, 1186 category: diagnosticCategoryName(d), 1187 code: d.code, 1188 source: d.source, 1189 startLocation: scriptInfo && scriptInfo.positionToLineOffset(d.start!), // TODO: GH#18217 1190 endLocation: scriptInfo && scriptInfo.positionToLineOffset(d.start! + d.length!), 1191 reportsUnnecessary: d.reportsUnnecessary, 1192 reportsDeprecated: d.reportsDeprecated, 1193 relatedInformation: map(d.relatedInformation, formatRelatedInformation), 1194 }); 1195 } 1196 1197 private getDiagnosticsWorker( 1198 args: protocol.FileRequestArgs, isSemantic: boolean, selector: (project: Project, file: string) => readonly Diagnostic[], includeLinePosition: boolean 1199 ): readonly protocol.DiagnosticWithLinePosition[] | readonly protocol.Diagnostic[] { 1200 const { project, file } = this.getFileAndProject(args); 1201 if (isSemantic && isDeclarationFileInJSOnlyNonConfiguredProject(project, file)) { 1202 return emptyArray; 1203 } 1204 const scriptInfo = project.getScriptInfoForNormalizedPath(file); 1205 const diagnostics = selector(project, file); 1206 return includeLinePosition 1207 ? this.convertToDiagnosticsWithLinePosition(diagnostics, scriptInfo) 1208 : diagnostics.map(d => formatDiag(file, project, d)); 1209 } 1210 1211 private getDefinition(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): readonly protocol.FileSpanWithContext[] | readonly DefinitionInfo[] { 1212 const { file, project } = this.getFileAndProject(args); 1213 const position = this.getPositionInFile(args, file); 1214 const definitions = this.mapDefinitionInfoLocations(project.getLanguageService().getDefinitionAtPosition(file, position) || emptyArray, project); 1215 return simplifiedResult ? this.mapDefinitionInfo(definitions, project) : definitions.map(Session.mapToOriginalLocation); 1216 } 1217 1218 private mapDefinitionInfoLocations(definitions: readonly DefinitionInfo[], project: Project): readonly DefinitionInfo[] { 1219 return definitions.map((info): DefinitionInfo => { 1220 const newDocumentSpan = getMappedDocumentSpan(info, project); 1221 return !newDocumentSpan ? info : { 1222 ...newDocumentSpan, 1223 containerKind: info.containerKind, 1224 containerName: info.containerName, 1225 kind: info.kind, 1226 name: info.name, 1227 }; 1228 }); 1229 } 1230 1231 private getDefinitionAndBoundSpan(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): protocol.DefinitionInfoAndBoundSpan | DefinitionInfoAndBoundSpan { 1232 const { file, project } = this.getFileAndProject(args); 1233 const position = this.getPositionInFile(args, file); 1234 const scriptInfo = Debug.checkDefined(project.getScriptInfo(file)); 1235 1236 const unmappedDefinitionAndBoundSpan = project.getLanguageService().getDefinitionAndBoundSpan(file, position); 1237 1238 if (!unmappedDefinitionAndBoundSpan || !unmappedDefinitionAndBoundSpan.definitions) { 1239 return { 1240 definitions: emptyArray, 1241 textSpan: undefined! // TODO: GH#18217 1242 }; 1243 } 1244 1245 const definitions = this.mapDefinitionInfoLocations(unmappedDefinitionAndBoundSpan.definitions, project); 1246 const { textSpan } = unmappedDefinitionAndBoundSpan; 1247 1248 if (simplifiedResult) { 1249 return { 1250 definitions: this.mapDefinitionInfo(definitions, project), 1251 textSpan: toProtocolTextSpan(textSpan, scriptInfo) 1252 }; 1253 } 1254 1255 return { 1256 definitions: definitions.map(Session.mapToOriginalLocation), 1257 textSpan, 1258 }; 1259 } 1260 1261 private getEmitOutput(args: protocol.EmitOutputRequestArgs): EmitOutput | protocol.EmitOutput { 1262 const { file, project } = this.getFileAndProject(args); 1263 if (!project.shouldEmitFile(project.getScriptInfo(file))) { 1264 return { emitSkipped: true, outputFiles: [], diagnostics: [] }; 1265 } 1266 const result = project.getLanguageService().getEmitOutput(file); 1267 return args.richResponse ? 1268 { 1269 ...result, 1270 diagnostics: args.includeLinePosition ? 1271 this.convertToDiagnosticsWithLinePositionFromDiagnosticFile(result.diagnostics) : 1272 result.diagnostics.map(d => formatDiagnosticToProtocol(d, /*includeFileName*/ true)) 1273 } : 1274 result; 1275 } 1276 1277 private mapDefinitionInfo(definitions: readonly DefinitionInfo[], project: Project): readonly protocol.FileSpanWithContext[] { 1278 return definitions.map(def => this.toFileSpanWithContext(def.fileName, def.textSpan, def.contextSpan, project)); 1279 } 1280 1281 /* 1282 * When we map a .d.ts location to .ts, Visual Studio gets confused because there's no associated Roslyn Document in 1283 * the same project which corresponds to the file. VS Code has no problem with this, and luckily we have two protocols. 1284 * This retains the existing behavior for the "simplified" (VS Code) protocol but stores the .d.ts location in a 1285 * set of additional fields, and does the reverse for VS (store the .d.ts location where 1286 * it used to be and stores the .ts location in the additional fields). 1287 */ 1288 private static mapToOriginalLocation<T extends DocumentSpan>(def: T): T { 1289 if (def.originalFileName) { 1290 Debug.assert(def.originalTextSpan !== undefined, "originalTextSpan should be present if originalFileName is"); 1291 return { 1292 ...<any>def, 1293 fileName: def.originalFileName, 1294 textSpan: def.originalTextSpan, 1295 targetFileName: def.fileName, 1296 targetTextSpan: def.textSpan, 1297 contextSpan: def.originalContextSpan, 1298 targetContextSpan: def.contextSpan 1299 }; 1300 } 1301 return def; 1302 } 1303 1304 private toFileSpan(fileName: string, textSpan: TextSpan, project: Project): protocol.FileSpan { 1305 const ls = project.getLanguageService(); 1306 const start = ls.toLineColumnOffset!(fileName, textSpan.start); // TODO: GH#18217 1307 const end = ls.toLineColumnOffset!(fileName, textSpanEnd(textSpan)); 1308 1309 return { 1310 file: fileName, 1311 start: { line: start.line + 1, offset: start.character + 1 }, 1312 end: { line: end.line + 1, offset: end.character + 1 } 1313 }; 1314 } 1315 1316 private toFileSpanWithContext(fileName: string, textSpan: TextSpan, contextSpan: TextSpan | undefined, project: Project): protocol.FileSpanWithContext { 1317 const fileSpan = this.toFileSpan(fileName, textSpan, project); 1318 const context = contextSpan && this.toFileSpan(fileName, contextSpan, project); 1319 return context ? 1320 { ...fileSpan, contextStart: context.start, contextEnd: context.end } : 1321 fileSpan; 1322 } 1323 1324 private getTypeDefinition(args: protocol.FileLocationRequestArgs): readonly protocol.FileSpanWithContext[] { 1325 const { file, project } = this.getFileAndProject(args); 1326 const position = this.getPositionInFile(args, file); 1327 1328 const definitions = this.mapDefinitionInfoLocations(project.getLanguageService().getTypeDefinitionAtPosition(file, position) || emptyArray, project); 1329 return this.mapDefinitionInfo(definitions, project); 1330 } 1331 1332 private mapImplementationLocations(implementations: readonly ImplementationLocation[], project: Project): readonly ImplementationLocation[] { 1333 return implementations.map((info): ImplementationLocation => { 1334 const newDocumentSpan = getMappedDocumentSpan(info, project); 1335 return !newDocumentSpan ? info : { 1336 ...newDocumentSpan, 1337 kind: info.kind, 1338 displayParts: info.displayParts, 1339 }; 1340 }); 1341 } 1342 1343 private getImplementation(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): readonly protocol.FileSpanWithContext[] | readonly ImplementationLocation[] { 1344 const { file, project } = this.getFileAndProject(args); 1345 const position = this.getPositionInFile(args, file); 1346 const implementations = this.mapImplementationLocations(project.getLanguageService().getImplementationAtPosition(file, position) || emptyArray, project); 1347 return simplifiedResult ? 1348 implementations.map(({ fileName, textSpan, contextSpan }) => this.toFileSpanWithContext(fileName, textSpan, contextSpan, project)) : 1349 implementations.map(Session.mapToOriginalLocation); 1350 } 1351 1352 private getOccurrences(args: protocol.FileLocationRequestArgs): readonly protocol.OccurrencesResponseItem[] { 1353 const { file, project } = this.getFileAndProject(args); 1354 const position = this.getPositionInFile(args, file); 1355 const occurrences = project.getLanguageService().getOccurrencesAtPosition(file, position); 1356 return occurrences ? 1357 occurrences.map<protocol.OccurrencesResponseItem>(occurrence => { 1358 const { fileName, isWriteAccess, textSpan, isInString, contextSpan } = occurrence; 1359 const scriptInfo = project.getScriptInfo(fileName)!; 1360 return { 1361 ...toProtocolTextSpanWithContext(textSpan, contextSpan, scriptInfo), 1362 file: fileName, 1363 isWriteAccess, 1364 ...(isInString ? { isInString } : undefined) 1365 }; 1366 }) : 1367 emptyArray; 1368 } 1369 1370 private getSyntacticDiagnosticsSync(args: protocol.SyntacticDiagnosticsSyncRequestArgs): readonly protocol.Diagnostic[] | readonly protocol.DiagnosticWithLinePosition[] { 1371 const { configFile } = this.getConfigFileAndProject(args); 1372 if (configFile) { 1373 // all the config file errors are reported as part of semantic check so nothing to report here 1374 return emptyArray; 1375 } 1376 1377 return this.getDiagnosticsWorker(args, /*isSemantic*/ false, (project, file) => project.getLanguageService().getSyntacticDiagnostics(file), !!args.includeLinePosition); 1378 } 1379 1380 private getSemanticDiagnosticsSync(args: protocol.SemanticDiagnosticsSyncRequestArgs): readonly protocol.Diagnostic[] | readonly protocol.DiagnosticWithLinePosition[] { 1381 const { configFile, project } = this.getConfigFileAndProject(args); 1382 if (configFile) { 1383 return this.getConfigFileDiagnostics(configFile, project!, !!args.includeLinePosition); // TODO: GH#18217 1384 } 1385 return this.getDiagnosticsWorker(args, /*isSemantic*/ true, (project, file) => project.getLanguageService().getSemanticDiagnostics(file).filter(d => !!d.file), !!args.includeLinePosition); 1386 } 1387 1388 private getSuggestionDiagnosticsSync(args: protocol.SuggestionDiagnosticsSyncRequestArgs): readonly protocol.Diagnostic[] | readonly protocol.DiagnosticWithLinePosition[] { 1389 const { configFile } = this.getConfigFileAndProject(args); 1390 if (configFile) { 1391 // Currently there are no info diagnostics for config files. 1392 return emptyArray; 1393 } 1394 // isSemantic because we don't want to info diagnostics in declaration files for JS-only users 1395 return this.getDiagnosticsWorker(args, /*isSemantic*/ true, (project, file) => project.getLanguageService().getSuggestionDiagnostics(file), !!args.includeLinePosition); 1396 } 1397 1398 private getJsxClosingTag(args: protocol.JsxClosingTagRequestArgs): TextInsertion | undefined { 1399 const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); 1400 const position = this.getPositionInFile(args, file); 1401 const tag = languageService.getJsxClosingTagAtPosition(file, position); 1402 return tag === undefined ? undefined : { newText: tag.newText, caretOffset: 0 }; 1403 } 1404 1405 private getDocumentHighlights(args: protocol.DocumentHighlightsRequestArgs, simplifiedResult: boolean): readonly protocol.DocumentHighlightsItem[] | readonly DocumentHighlights[] { 1406 const { file, project } = this.getFileAndProject(args); 1407 const position = this.getPositionInFile(args, file); 1408 const documentHighlights = project.getLanguageService().getDocumentHighlights(file, position, args.filesToSearch); 1409 1410 if (!documentHighlights) return emptyArray; 1411 if (!simplifiedResult) return documentHighlights; 1412 1413 return documentHighlights.map<protocol.DocumentHighlightsItem>(({ fileName, highlightSpans }) => { 1414 const scriptInfo = project.getScriptInfo(fileName)!; 1415 return { 1416 file: fileName, 1417 highlightSpans: highlightSpans.map(({ textSpan, kind, contextSpan }) => ({ 1418 ...toProtocolTextSpanWithContext(textSpan, contextSpan, scriptInfo), 1419 kind 1420 })) 1421 }; 1422 }); 1423 } 1424 1425 private setCompilerOptionsForInferredProjects(args: protocol.SetCompilerOptionsForInferredProjectsArgs): void { 1426 this.projectService.setCompilerOptionsForInferredProjects(args.options, args.projectRootPath); 1427 } 1428 1429 private getProjectInfo(args: protocol.ProjectInfoRequestArgs): protocol.ProjectInfo { 1430 return this.getProjectInfoWorker(args.file, args.projectFileName, args.needFileNameList, /*excludeConfigFiles*/ false); 1431 } 1432 1433 private getProjectInfoWorker(uncheckedFileName: string, projectFileName: string | undefined, needFileNameList: boolean, excludeConfigFiles: boolean) { 1434 const { project } = this.getFileAndProjectWorker(uncheckedFileName, projectFileName); 1435 updateProjectIfDirty(project); 1436 const projectInfo = { 1437 configFileName: project.getProjectName(), 1438 languageServiceDisabled: !project.languageServiceEnabled, 1439 fileNames: needFileNameList ? project.getFileNames(/*excludeFilesFromExternalLibraries*/ false, excludeConfigFiles) : undefined 1440 }; 1441 return projectInfo; 1442 } 1443 1444 private getRenameInfo(args: protocol.FileLocationRequestArgs): RenameInfo { 1445 const { file, project } = this.getFileAndProject(args); 1446 const position = this.getPositionInFile(args, file); 1447 return project.getLanguageService().getRenameInfo(file, position, { allowRenameOfImportPath: this.getPreferences(file).allowRenameOfImportPath }); 1448 } 1449 1450 private getProjects(args: protocol.FileRequestArgs, getScriptInfoEnsuringProjectsUptoDate?: boolean, ignoreNoProjectError?: boolean): Projects { 1451 let projects: readonly Project[] | undefined; 1452 let symLinkedProjects: MultiMap<Path, Project> | undefined; 1453 if (args.projectFileName) { 1454 const project = this.getProject(args.projectFileName); 1455 if (project) { 1456 projects = [project]; 1457 } 1458 } 1459 else { 1460 const scriptInfo = getScriptInfoEnsuringProjectsUptoDate ? 1461 this.projectService.getScriptInfoEnsuringProjectsUptoDate(args.file) : 1462 this.projectService.getScriptInfo(args.file); 1463 if (!scriptInfo) { 1464 if (ignoreNoProjectError) return emptyArray; 1465 this.projectService.logErrorForScriptInfoNotFound(args.file); 1466 return Errors.ThrowNoProject(); 1467 } 1468 projects = scriptInfo.containingProjects; 1469 symLinkedProjects = this.projectService.getSymlinkedProjects(scriptInfo); 1470 } 1471 // filter handles case when 'projects' is undefined 1472 projects = filter(projects, p => p.languageServiceEnabled && !p.isOrphan()); 1473 if (!ignoreNoProjectError && (!projects || !projects.length) && !symLinkedProjects) { 1474 this.projectService.logErrorForScriptInfoNotFound(args.file ?? args.projectFileName); 1475 return Errors.ThrowNoProject(); 1476 } 1477 return symLinkedProjects ? { projects: projects!, symLinkedProjects } : projects!; // TODO: GH#18217 1478 } 1479 1480 private getDefaultProject(args: protocol.FileRequestArgs) { 1481 if (args.projectFileName) { 1482 const project = this.getProject(args.projectFileName); 1483 if (project) { 1484 return project; 1485 } 1486 if (!args.file) { 1487 return Errors.ThrowNoProject(); 1488 } 1489 } 1490 const info = this.projectService.getScriptInfo(args.file)!; 1491 return info.getDefaultProject(); 1492 } 1493 1494 private getRenameLocations(args: protocol.RenameRequestArgs, simplifiedResult: boolean): protocol.RenameResponseBody | readonly RenameLocation[] { 1495 const file = toNormalizedPath(args.file); 1496 const position = this.getPositionInFile(args, file); 1497 const projects = this.getProjects(args); 1498 1499 const locations = combineProjectOutputForRenameLocations( 1500 projects, 1501 this.getDefaultProject(args), 1502 { fileName: args.file, pos: position }, 1503 !!args.findInStrings, 1504 !!args.findInComments, 1505 this.getPreferences(file) 1506 ); 1507 if (!simplifiedResult) return locations; 1508 1509 const defaultProject = this.getDefaultProject(args); 1510 const renameInfo: protocol.RenameInfo = this.mapRenameInfo(defaultProject.getLanguageService().getRenameInfo(file, position, { allowRenameOfImportPath: this.getPreferences(file).allowRenameOfImportPath }), Debug.checkDefined(this.projectService.getScriptInfo(file))); 1511 return { info: renameInfo, locs: this.toSpanGroups(locations) }; 1512 } 1513 1514 private mapRenameInfo(info: RenameInfo, scriptInfo: ScriptInfo): protocol.RenameInfo { 1515 if (info.canRename) { 1516 const { canRename, fileToRename, displayName, fullDisplayName, kind, kindModifiers, triggerSpan } = info; 1517 return identity<protocol.RenameInfoSuccess>( 1518 { canRename, fileToRename, displayName, fullDisplayName, kind, kindModifiers, triggerSpan: toProtocolTextSpan(triggerSpan, scriptInfo) }); 1519 } 1520 else { 1521 return info; 1522 } 1523 } 1524 1525 private toSpanGroups(locations: readonly RenameLocation[]): readonly protocol.SpanGroup[] { 1526 const map = new Map<string, protocol.SpanGroup>(); 1527 for (const { fileName, textSpan, contextSpan, originalContextSpan: _2, originalTextSpan: _, originalFileName: _1, ...prefixSuffixText } of locations) { 1528 let group = map.get(fileName); 1529 if (!group) map.set(fileName, group = { file: fileName, locs: [] }); 1530 const scriptInfo = Debug.checkDefined(this.projectService.getScriptInfo(fileName)); 1531 group.locs.push({ ...toProtocolTextSpanWithContext(textSpan, contextSpan, scriptInfo), ...prefixSuffixText }); 1532 } 1533 return arrayFrom(map.values()); 1534 } 1535 1536 private getReferences(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): protocol.ReferencesResponseBody | readonly ReferencedSymbol[] { 1537 const file = toNormalizedPath(args.file); 1538 const projects = this.getProjects(args); 1539 const position = this.getPositionInFile(args, file); 1540 const references = combineProjectOutputForReferences( 1541 projects, 1542 this.getDefaultProject(args), 1543 { fileName: args.file, pos: position }, 1544 ); 1545 1546 if (!simplifiedResult) return references; 1547 1548 const defaultProject = this.getDefaultProject(args); 1549 const scriptInfo = defaultProject.getScriptInfoForNormalizedPath(file)!; 1550 const nameInfo = defaultProject.getLanguageService().getQuickInfoAtPosition(file, position); 1551 const symbolDisplayString = nameInfo ? displayPartsToString(nameInfo.displayParts) : ""; 1552 const nameSpan = nameInfo && nameInfo.textSpan; 1553 const symbolStartOffset = nameSpan ? scriptInfo.positionToLineOffset(nameSpan.start).offset : 0; 1554 const symbolName = nameSpan ? scriptInfo.getSnapshot().getText(nameSpan.start, textSpanEnd(nameSpan)) : ""; 1555 const refs: readonly protocol.ReferencesResponseItem[] = flatMap(references, referencedSymbol => { 1556 return referencedSymbol.references.map(entry => referenceEntryToReferencesResponseItem(this.projectService, entry)); 1557 }); 1558 return { refs, symbolName, symbolStartOffset, symbolDisplayString }; 1559 } 1560 1561 private getFileReferences(args: protocol.FileRequestArgs, simplifiedResult: boolean): protocol.FileReferencesResponseBody | readonly ReferenceEntry[] { 1562 const projects = this.getProjects(args); 1563 const references = combineProjectOutputForFileReferences( 1564 projects, 1565 this.getDefaultProject(args), 1566 args.file, 1567 ); 1568 1569 if (!simplifiedResult) return references; 1570 const refs = references.map(entry => referenceEntryToReferencesResponseItem(this.projectService, entry)); 1571 return { 1572 refs, 1573 symbolName: `"${args.file}"` 1574 }; 1575 } 1576 1577 /** 1578 * @param fileName is the name of the file to be opened 1579 * @param fileContent is a version of the file content that is known to be more up to date than the one on disk 1580 */ 1581 private openClientFile(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind, projectRootPath?: NormalizedPath) { 1582 this.projectService.openClientFileWithNormalizedPath(fileName, fileContent, scriptKind, /*hasMixedContent*/ false, projectRootPath); 1583 } 1584 1585 private getPosition(args: protocol.Location & { position?: number }, scriptInfo: ScriptInfo): number { 1586 return args.position !== undefined ? args.position : scriptInfo.lineOffsetToPosition(args.line, args.offset); 1587 } 1588 1589 private getPositionInFile(args: protocol.Location & { position?: number }, file: NormalizedPath): number { 1590 const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; 1591 return this.getPosition(args, scriptInfo); 1592 } 1593 1594 private getFileAndProject(args: protocol.FileRequestArgs): FileAndProject { 1595 return this.getFileAndProjectWorker(args.file, args.projectFileName); 1596 } 1597 1598 private getFileAndLanguageServiceForSyntacticOperation(args: protocol.FileRequestArgs) { 1599 // Since this is syntactic operation, there should always be project for the file 1600 // we wouldnt have to ensure project but rather throw if we dont get project 1601 const file = toNormalizedPath(args.file); 1602 const project = this.getProject(args.projectFileName) || this.projectService.tryGetDefaultProjectForFile(file); 1603 if (!project) { 1604 return Errors.ThrowNoProject(); 1605 } 1606 return { 1607 file, 1608 languageService: project.getLanguageService(/*ensureSynchronized*/ false) 1609 }; 1610 } 1611 1612 private getFileAndProjectWorker(uncheckedFileName: string, projectFileName: string | undefined): { file: NormalizedPath, project: Project } { 1613 const file = toNormalizedPath(uncheckedFileName); 1614 const project = this.getProject(projectFileName) || this.projectService.ensureDefaultProjectForFile(file); 1615 return { file, project }; 1616 } 1617 1618 private getOutliningSpans(args: protocol.FileRequestArgs, simplifiedResult: boolean): protocol.OutliningSpan[] | OutliningSpan[] { 1619 const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); 1620 const spans = languageService.getOutliningSpans(file); 1621 if (simplifiedResult) { 1622 const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; 1623 return spans.map(s => ({ 1624 textSpan: toProtocolTextSpan(s.textSpan, scriptInfo), 1625 hintSpan: toProtocolTextSpan(s.hintSpan, scriptInfo), 1626 bannerText: s.bannerText, 1627 autoCollapse: s.autoCollapse, 1628 kind: s.kind 1629 })); 1630 } 1631 else { 1632 return spans; 1633 } 1634 } 1635 1636 private getTodoComments(args: protocol.TodoCommentRequestArgs) { 1637 const { file, project } = this.getFileAndProject(args); 1638 return project.getLanguageService().getTodoComments(file, args.descriptors); 1639 } 1640 1641 private getDocCommentTemplate(args: protocol.FileLocationRequestArgs) { 1642 const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); 1643 const position = this.getPositionInFile(args, file); 1644 return languageService.getDocCommentTemplateAtPosition(file, position, this.getPreferences(file)); 1645 } 1646 1647 private getSpanOfEnclosingComment(args: protocol.SpanOfEnclosingCommentRequestArgs) { 1648 const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); 1649 const onlyMultiLine = args.onlyMultiLine; 1650 const position = this.getPositionInFile(args, file); 1651 return languageService.getSpanOfEnclosingComment(file, position, onlyMultiLine); 1652 } 1653 1654 private getIndentation(args: protocol.IndentationRequestArgs) { 1655 const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); 1656 const position = this.getPositionInFile(args, file); 1657 const options = args.options ? convertFormatOptions(args.options) : this.getFormatOptions(file); 1658 const indentation = languageService.getIndentationAtPosition(file, position, options); 1659 return { position, indentation }; 1660 } 1661 1662 private getBreakpointStatement(args: protocol.FileLocationRequestArgs) { 1663 const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); 1664 const position = this.getPositionInFile(args, file); 1665 return languageService.getBreakpointStatementAtPosition(file, position); 1666 } 1667 1668 private getNameOrDottedNameSpan(args: protocol.FileLocationRequestArgs) { 1669 const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); 1670 const position = this.getPositionInFile(args, file); 1671 return languageService.getNameOrDottedNameSpan(file, position, position); 1672 } 1673 1674 private isValidBraceCompletion(args: protocol.BraceCompletionRequestArgs) { 1675 const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); 1676 const position = this.getPositionInFile(args, file); 1677 return languageService.isValidBraceCompletionAtPosition(file, position, args.openingBrace.charCodeAt(0)); 1678 } 1679 1680 private getQuickInfoWorker(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): protocol.QuickInfoResponseBody | QuickInfo | undefined { 1681 const { file, project } = this.getFileAndProject(args); 1682 const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; 1683 const quickInfo = project.getLanguageService().getQuickInfoAtPosition(file, this.getPosition(args, scriptInfo)); 1684 if (!quickInfo) { 1685 return undefined; 1686 } 1687 1688 if (simplifiedResult) { 1689 const displayString = displayPartsToString(quickInfo.displayParts); 1690 const docString = displayPartsToString(quickInfo.documentation); 1691 1692 return { 1693 kind: quickInfo.kind, 1694 kindModifiers: quickInfo.kindModifiers, 1695 start: scriptInfo.positionToLineOffset(quickInfo.textSpan.start), 1696 end: scriptInfo.positionToLineOffset(textSpanEnd(quickInfo.textSpan)), 1697 displayString, 1698 documentation: docString, 1699 tags: quickInfo.tags || [] 1700 }; 1701 } 1702 else { 1703 return quickInfo; 1704 } 1705 } 1706 1707 private getFormattingEditsForRange(args: protocol.FormatRequestArgs): protocol.CodeEdit[] | undefined { 1708 const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); 1709 const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; 1710 1711 const startPosition = scriptInfo.lineOffsetToPosition(args.line, args.offset); 1712 const endPosition = scriptInfo.lineOffsetToPosition(args.endLine, args.endOffset); 1713 1714 // TODO: avoid duplicate code (with formatonkey) 1715 const edits = languageService.getFormattingEditsForRange(file, startPosition, endPosition, this.getFormatOptions(file)); 1716 if (!edits) { 1717 return undefined; 1718 } 1719 1720 return edits.map(edit => this.convertTextChangeToCodeEdit(edit, scriptInfo)); 1721 } 1722 1723 private getFormattingEditsForRangeFull(args: protocol.FormatRequestArgs) { 1724 const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); 1725 const options = args.options ? convertFormatOptions(args.options) : this.getFormatOptions(file); 1726 return languageService.getFormattingEditsForRange(file, args.position!, args.endPosition!, options); // TODO: GH#18217 1727 } 1728 1729 private getFormattingEditsForDocumentFull(args: protocol.FormatRequestArgs) { 1730 const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); 1731 const options = args.options ? convertFormatOptions(args.options) : this.getFormatOptions(file); 1732 return languageService.getFormattingEditsForDocument(file, options); 1733 } 1734 1735 private getFormattingEditsAfterKeystrokeFull(args: protocol.FormatOnKeyRequestArgs) { 1736 const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); 1737 const options = args.options ? convertFormatOptions(args.options) : this.getFormatOptions(file); 1738 return languageService.getFormattingEditsAfterKeystroke(file, args.position!, args.key, options); // TODO: GH#18217 1739 } 1740 1741 private getFormattingEditsAfterKeystroke(args: protocol.FormatOnKeyRequestArgs): protocol.CodeEdit[] | undefined { 1742 const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); 1743 const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; 1744 const position = scriptInfo.lineOffsetToPosition(args.line, args.offset); 1745 const formatOptions = this.getFormatOptions(file); 1746 const edits = languageService.getFormattingEditsAfterKeystroke(file, position, args.key, 1747 formatOptions); 1748 // Check whether we should auto-indent. This will be when 1749 // the position is on a line containing only whitespace. 1750 // This should leave the edits returned from 1751 // getFormattingEditsAfterKeystroke either empty or pertaining 1752 // only to the previous line. If all this is true, then 1753 // add edits necessary to properly indent the current line. 1754 if ((args.key === "\n") && ((!edits) || (edits.length === 0) || allEditsBeforePos(edits, position))) { 1755 const { lineText, absolutePosition } = scriptInfo.getAbsolutePositionAndLineText(args.line); 1756 if (lineText && lineText.search("\\S") < 0) { 1757 const preferredIndent = languageService.getIndentationAtPosition(file, position, formatOptions); 1758 let hasIndent = 0; 1759 let i: number, len: number; 1760 for (i = 0, len = lineText.length; i < len; i++) { 1761 if (lineText.charAt(i) === " ") { 1762 hasIndent++; 1763 } 1764 else if (lineText.charAt(i) === "\t") { 1765 hasIndent += formatOptions.tabSize!; // TODO: GH#18217 1766 } 1767 else { 1768 break; 1769 } 1770 } 1771 // i points to the first non whitespace character 1772 if (preferredIndent !== hasIndent) { 1773 const firstNoWhiteSpacePosition = absolutePosition + i; 1774 edits.push({ 1775 span: createTextSpanFromBounds(absolutePosition, firstNoWhiteSpacePosition), 1776 newText: formatting.getIndentationString(preferredIndent, formatOptions) 1777 }); 1778 } 1779 } 1780 } 1781 1782 if (!edits) { 1783 return undefined; 1784 } 1785 1786 return edits.map((edit) => { 1787 return { 1788 start: scriptInfo.positionToLineOffset(edit.span.start), 1789 end: scriptInfo.positionToLineOffset(textSpanEnd(edit.span)), 1790 newText: edit.newText ? edit.newText : "" 1791 }; 1792 }); 1793 } 1794 1795 private getCompletions(args: protocol.CompletionsRequestArgs, kind: protocol.CommandTypes.CompletionInfo | protocol.CommandTypes.Completions | protocol.CommandTypes.CompletionsFull): WithMetadata<readonly protocol.CompletionEntry[]> | protocol.CompletionInfo | CompletionInfo | undefined { 1796 const { file, project } = this.getFileAndProject(args); 1797 const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; 1798 const position = this.getPosition(args, scriptInfo); 1799 1800 const completions = project.getLanguageService().getCompletionsAtPosition(file, position, { 1801 ...convertUserPreferences(this.getPreferences(file)), 1802 triggerCharacter: args.triggerCharacter, 1803 includeExternalModuleExports: args.includeExternalModuleExports, 1804 includeInsertTextCompletions: args.includeInsertTextCompletions 1805 }); 1806 if (completions === undefined) return undefined; 1807 1808 if (kind === protocol.CommandTypes.CompletionsFull) return completions; 1809 1810 const prefix = args.prefix || ""; 1811 const entries = mapDefined<CompletionEntry, protocol.CompletionEntry>(completions.entries, entry => { 1812 if (completions.isMemberCompletion || startsWith(entry.name.toLowerCase(), prefix.toLowerCase())) { 1813 const { name, kind, kindModifiers, sortText, insertText, replacementSpan, hasAction, source, isRecommended, isPackageJsonImport, jsDoc, displayParts } = entry; 1814 const convertedSpan = replacementSpan ? toProtocolTextSpan(replacementSpan, scriptInfo) : undefined; 1815 // Use `hasAction || undefined` to avoid serializing `false`. 1816 return { name, kind, kindModifiers, sortText, insertText, replacementSpan: convertedSpan, hasAction: hasAction || undefined, source, isRecommended, isPackageJsonImport, jsDoc, displayParts }; 1817 } 1818 }).sort((a, b) => compareStringsCaseSensitiveUI(a.name, b.name)); 1819 1820 if (kind === protocol.CommandTypes.Completions) { 1821 if (completions.metadata) (entries as WithMetadata<readonly protocol.CompletionEntry[]>).metadata = completions.metadata; 1822 return entries; 1823 } 1824 1825 const res: protocol.CompletionInfo = { 1826 ...completions, 1827 optionalReplacementSpan: completions.optionalReplacementSpan && toProtocolTextSpan(completions.optionalReplacementSpan, scriptInfo), 1828 entries, 1829 }; 1830 return res; 1831 } 1832 1833 private getCompletionEntryDetails(args: protocol.CompletionDetailsRequestArgs, simplifiedResult: boolean): readonly protocol.CompletionEntryDetails[] | readonly CompletionEntryDetails[] { 1834 const { file, project } = this.getFileAndProject(args); 1835 const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; 1836 const position = this.getPosition(args, scriptInfo); 1837 const formattingOptions = project.projectService.getFormatCodeOptions(file); 1838 1839 const result = mapDefined(args.entryNames, entryName => { 1840 const { name, source } = typeof entryName === "string" ? { name: entryName, source: undefined } : entryName; 1841 return project.getLanguageService().getCompletionEntryDetails(file, position, name, formattingOptions, source, this.getPreferences(file)); 1842 }); 1843 return simplifiedResult 1844 ? result.map(details => ({ ...details, codeActions: map(details.codeActions, action => this.mapCodeAction(action)) })) 1845 : result; 1846 } 1847 1848 private getCompileOnSaveAffectedFileList(args: protocol.FileRequestArgs): readonly protocol.CompileOnSaveAffectedFileListSingleProject[] { 1849 const projects = this.getProjects(args, /*getScriptInfoEnsuringProjectsUptoDate*/ true, /*ignoreNoProjectError*/ true); 1850 const info = this.projectService.getScriptInfo(args.file); 1851 if (!info) { 1852 return emptyArray; 1853 } 1854 1855 return combineProjectOutput( 1856 info, 1857 path => this.projectService.getScriptInfoForPath(path)!, 1858 projects, 1859 (project, info) => { 1860 if (!project.compileOnSaveEnabled || !project.languageServiceEnabled || project.isOrphan()) { 1861 return undefined; 1862 } 1863 1864 const compilationSettings = project.getCompilationSettings(); 1865 1866 if (!!compilationSettings.noEmit || isDeclarationFileName(info.fileName) && !dtsChangeCanAffectEmit(compilationSettings)) { 1867 // avoid triggering emit when a change is made in a .d.ts when declaration emit and decorator metadata emit are disabled 1868 return undefined; 1869 } 1870 1871 return { 1872 projectFileName: project.getProjectName(), 1873 fileNames: project.getCompileOnSaveAffectedFileList(info), 1874 projectUsesOutFile: !!outFile(compilationSettings) 1875 }; 1876 } 1877 ); 1878 } 1879 1880 private emitFile(args: protocol.CompileOnSaveEmitFileRequestArgs): boolean | protocol.EmitResult | EmitResult { 1881 const { file, project } = this.getFileAndProject(args); 1882 if (!project) { 1883 Errors.ThrowNoProject(); 1884 } 1885 if (!project.languageServiceEnabled) { 1886 return args.richResponse ? { emitSkipped: true, diagnostics: [] } : false; 1887 } 1888 const scriptInfo = project.getScriptInfo(file)!; 1889 const { emitSkipped, diagnostics } = project.emitFile(scriptInfo, (path, data, writeByteOrderMark) => this.host.writeFile(path, data, writeByteOrderMark)); 1890 return args.richResponse ? 1891 { 1892 emitSkipped, 1893 diagnostics: args.includeLinePosition ? 1894 this.convertToDiagnosticsWithLinePositionFromDiagnosticFile(diagnostics) : 1895 diagnostics.map(d => formatDiagnosticToProtocol(d, /*includeFileName*/ true)) 1896 } : 1897 !emitSkipped; 1898 } 1899 1900 private getSignatureHelpItems(args: protocol.SignatureHelpRequestArgs, simplifiedResult: boolean): protocol.SignatureHelpItems | SignatureHelpItems | undefined { 1901 const { file, project } = this.getFileAndProject(args); 1902 const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; 1903 const position = this.getPosition(args, scriptInfo); 1904 const helpItems = project.getLanguageService().getSignatureHelpItems(file, position, args); 1905 if (!helpItems) { 1906 return undefined; 1907 } 1908 1909 if (simplifiedResult) { 1910 const span = helpItems.applicableSpan; 1911 return { 1912 items: helpItems.items, 1913 applicableSpan: { 1914 start: scriptInfo.positionToLineOffset(span.start), 1915 end: scriptInfo.positionToLineOffset(span.start + span.length) 1916 }, 1917 selectedItemIndex: helpItems.selectedItemIndex, 1918 argumentIndex: helpItems.argumentIndex, 1919 argumentCount: helpItems.argumentCount, 1920 }; 1921 } 1922 else { 1923 return helpItems; 1924 } 1925 } 1926 1927 private toPendingErrorCheck(uncheckedFileName: string): PendingErrorCheck | undefined { 1928 const fileName = toNormalizedPath(uncheckedFileName); 1929 const project = this.projectService.tryGetDefaultProjectForFile(fileName); 1930 return project && { fileName, project }; 1931 } 1932 1933 private getDiagnostics(next: NextStep, delay: number, fileNames: string[]): void { 1934 if (this.suppressDiagnosticEvents) { 1935 return; 1936 } 1937 1938 if (fileNames.length > 0) { 1939 this.updateErrorCheck(next, fileNames, delay); 1940 } 1941 } 1942 1943 private change(args: protocol.ChangeRequestArgs) { 1944 const scriptInfo = this.projectService.getScriptInfo(args.file)!; 1945 Debug.assert(!!scriptInfo); 1946 const start = scriptInfo.lineOffsetToPosition(args.line, args.offset); 1947 const end = scriptInfo.lineOffsetToPosition(args.endLine, args.endOffset); 1948 if (start >= 0) { 1949 this.changeSeq++; 1950 this.projectService.applyChangesToFile(scriptInfo, singleIterator({ 1951 span: { start, length: end - start }, 1952 newText: args.insertString! // TODO: GH#18217 1953 })); 1954 } 1955 } 1956 1957 private reload(args: protocol.ReloadRequestArgs, reqSeq: number) { 1958 const file = toNormalizedPath(args.file); 1959 const tempFileName = args.tmpfile === undefined ? undefined : toNormalizedPath(args.tmpfile); 1960 const info = this.projectService.getScriptInfoForNormalizedPath(file); 1961 if (info) { 1962 this.changeSeq++; 1963 // make sure no changes happen before this one is finished 1964 if (info.reloadFromFile(tempFileName)) { 1965 this.doOutput(/*info*/ undefined, CommandNames.Reload, reqSeq, /*success*/ true); 1966 } 1967 } 1968 } 1969 1970 private saveToTmp(fileName: string, tempFileName: string) { 1971 const scriptInfo = this.projectService.getScriptInfo(fileName); 1972 if (scriptInfo) { 1973 scriptInfo.saveTo(tempFileName); 1974 } 1975 } 1976 1977 private closeClientFile(fileName: string) { 1978 if (!fileName) { 1979 return; 1980 } 1981 const file = normalizePath(fileName); 1982 this.projectService.closeClientFile(file); 1983 } 1984 1985 private mapLocationNavigationBarItems(items: NavigationBarItem[], scriptInfo: ScriptInfo): protocol.NavigationBarItem[] { 1986 return map(items, item => ({ 1987 text: item.text, 1988 kind: item.kind, 1989 kindModifiers: item.kindModifiers, 1990 spans: item.spans.map(span => toProtocolTextSpan(span, scriptInfo)), 1991 childItems: this.mapLocationNavigationBarItems(item.childItems, scriptInfo), 1992 indent: item.indent 1993 })); 1994 } 1995 1996 private getNavigationBarItems(args: protocol.FileRequestArgs, simplifiedResult: boolean): protocol.NavigationBarItem[] | NavigationBarItem[] | undefined { 1997 const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); 1998 const items = languageService.getNavigationBarItems(file); 1999 return !items 2000 ? undefined 2001 : simplifiedResult 2002 ? this.mapLocationNavigationBarItems(items, this.projectService.getScriptInfoForNormalizedPath(file)!) 2003 : items; 2004 } 2005 2006 private toLocationNavigationTree(tree: NavigationTree, scriptInfo: ScriptInfo): protocol.NavigationTree { 2007 return { 2008 text: tree.text, 2009 kind: tree.kind, 2010 kindModifiers: tree.kindModifiers, 2011 spans: tree.spans.map(span => toProtocolTextSpan(span, scriptInfo)), 2012 nameSpan: tree.nameSpan && toProtocolTextSpan(tree.nameSpan, scriptInfo), 2013 childItems: map(tree.childItems, item => this.toLocationNavigationTree(item, scriptInfo)) 2014 }; 2015 } 2016 2017 private getNavigationTree(args: protocol.FileRequestArgs, simplifiedResult: boolean): protocol.NavigationTree | NavigationTree | undefined { 2018 const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); 2019 const tree = languageService.getNavigationTree(file); 2020 return !tree 2021 ? undefined 2022 : simplifiedResult 2023 ? this.toLocationNavigationTree(tree, this.projectService.getScriptInfoForNormalizedPath(file)!) 2024 : tree; 2025 } 2026 2027 private getNavigateToItems(args: protocol.NavtoRequestArgs, simplifiedResult: boolean): readonly protocol.NavtoItem[] | readonly NavigateToItem[] { 2028 const full = this.getFullNavigateToItems(args); 2029 return !simplifiedResult ? 2030 flattenCombineOutputResult(full) : 2031 flatMap( 2032 full, 2033 ({ project, result }) => result.map(navItem => { 2034 const scriptInfo = project.getScriptInfo(navItem.fileName)!; 2035 const bakedItem: protocol.NavtoItem = { 2036 name: navItem.name, 2037 kind: navItem.kind, 2038 kindModifiers: navItem.kindModifiers, 2039 isCaseSensitive: navItem.isCaseSensitive, 2040 matchKind: navItem.matchKind, 2041 file: navItem.fileName, 2042 start: scriptInfo.positionToLineOffset(navItem.textSpan.start), 2043 end: scriptInfo.positionToLineOffset(textSpanEnd(navItem.textSpan)) 2044 }; 2045 if (navItem.kindModifiers && (navItem.kindModifiers !== "")) { 2046 bakedItem.kindModifiers = navItem.kindModifiers; 2047 } 2048 if (navItem.containerName && (navItem.containerName.length > 0)) { 2049 bakedItem.containerName = navItem.containerName; 2050 } 2051 if (navItem.containerKind && (navItem.containerKind.length > 0)) { 2052 bakedItem.containerKind = navItem.containerKind; 2053 } 2054 return bakedItem; 2055 }) 2056 ); 2057 } 2058 2059 private getFullNavigateToItems(args: protocol.NavtoRequestArgs): CombineOutputResult<NavigateToItem> { 2060 const { currentFileOnly, searchValue, maxResultCount, projectFileName } = args; 2061 if (currentFileOnly) { 2062 Debug.assertDefined(args.file); 2063 const { file, project } = this.getFileAndProject(args as protocol.FileRequestArgs); 2064 return [{ project, result: project.getLanguageService().getNavigateToItems(searchValue, maxResultCount, file) }]; 2065 } 2066 else if (!args.file && !projectFileName) { 2067 return combineProjectOutputFromEveryProject( 2068 this.projectService, 2069 project => project.getLanguageService().getNavigateToItems(searchValue, maxResultCount, /*filename*/ undefined, /*excludeDts*/ project.isNonTsProject()), 2070 navigateToItemIsEqualTo); 2071 } 2072 const fileArgs = args as protocol.FileRequestArgs; 2073 return combineProjectOutputWhileOpeningReferencedProjects<NavigateToItem>( 2074 this.getProjects(fileArgs), 2075 this.getDefaultProject(fileArgs), 2076 project => project.getLanguageService().getNavigateToItems(searchValue, maxResultCount, /*fileName*/ undefined, /*excludeDts*/ project.isNonTsProject()), 2077 documentSpanLocation, 2078 navigateToItemIsEqualTo); 2079 2080 function navigateToItemIsEqualTo(a: NavigateToItem, b: NavigateToItem): boolean { 2081 if (a === b) { 2082 return true; 2083 } 2084 if (!a || !b) { 2085 return false; 2086 } 2087 return a.containerKind === b.containerKind && 2088 a.containerName === b.containerName && 2089 a.fileName === b.fileName && 2090 a.isCaseSensitive === b.isCaseSensitive && 2091 a.kind === b.kind && 2092 a.kindModifiers === b.kindModifiers && 2093 a.matchKind === b.matchKind && 2094 a.name === b.name && 2095 a.textSpan.start === b.textSpan.start && 2096 a.textSpan.length === b.textSpan.length; 2097 } 2098 } 2099 2100 private getSupportedCodeFixes(): string[] { 2101 return getSupportedCodeFixes(); 2102 } 2103 2104 private isLocation(locationOrSpan: protocol.FileLocationOrRangeRequestArgs): locationOrSpan is protocol.FileLocationRequestArgs { 2105 return (<protocol.FileLocationRequestArgs>locationOrSpan).line !== undefined; 2106 } 2107 2108 private extractPositionOrRange(args: protocol.FileLocationOrRangeRequestArgs, scriptInfo: ScriptInfo): number | TextRange { 2109 let position: number | undefined; 2110 let textRange: TextRange | undefined; 2111 if (this.isLocation(args)) { 2112 position = getPosition(args); 2113 } 2114 else { 2115 textRange = this.getRange(args, scriptInfo); 2116 } 2117 return Debug.checkDefined(position === undefined ? textRange : position); 2118 2119 function getPosition(loc: protocol.FileLocationRequestArgs) { 2120 return loc.position !== undefined ? loc.position : scriptInfo.lineOffsetToPosition(loc.line, loc.offset); 2121 } 2122 } 2123 2124 private getRange(args: protocol.FileRangeRequestArgs, scriptInfo: ScriptInfo): TextRange { 2125 const { startPosition, endPosition } = this.getStartAndEndPosition(args, scriptInfo); 2126 2127 return { pos: startPosition, end: endPosition }; 2128 } 2129 2130 private getApplicableRefactors(args: protocol.GetApplicableRefactorsRequestArgs): protocol.ApplicableRefactorInfo[] { 2131 const { file, project } = this.getFileAndProject(args); 2132 const scriptInfo = project.getScriptInfoForNormalizedPath(file)!; 2133 return project.getLanguageService().getApplicableRefactors(file, this.extractPositionOrRange(args, scriptInfo), this.getPreferences(file), args.triggerReason, args.kind); 2134 } 2135 2136 private getEditsForRefactor(args: protocol.GetEditsForRefactorRequestArgs, simplifiedResult: boolean): RefactorEditInfo | protocol.RefactorEditInfo { 2137 const { file, project } = this.getFileAndProject(args); 2138 const scriptInfo = project.getScriptInfoForNormalizedPath(file)!; 2139 const result = project.getLanguageService().getEditsForRefactor( 2140 file, 2141 this.getFormatOptions(file), 2142 this.extractPositionOrRange(args, scriptInfo), 2143 args.refactor, 2144 args.action, 2145 this.getPreferences(file), 2146 ); 2147 2148 if (result === undefined) { 2149 return { 2150 edits: [] 2151 }; 2152 } 2153 2154 if (simplifiedResult) { 2155 const { renameFilename, renameLocation, edits } = result; 2156 let mappedRenameLocation: protocol.Location | undefined; 2157 if (renameFilename !== undefined && renameLocation !== undefined) { 2158 const renameScriptInfo = project.getScriptInfoForNormalizedPath(toNormalizedPath(renameFilename))!; 2159 mappedRenameLocation = getLocationInNewDocument(getSnapshotText(renameScriptInfo.getSnapshot()), renameFilename, renameLocation, edits); 2160 } 2161 return { renameLocation: mappedRenameLocation, renameFilename, edits: this.mapTextChangesToCodeEdits(edits) }; 2162 } 2163 else { 2164 return result; 2165 } 2166 } 2167 2168 private organizeImports({ scope }: protocol.OrganizeImportsRequestArgs, simplifiedResult: boolean): readonly protocol.FileCodeEdits[] | readonly FileTextChanges[] { 2169 Debug.assert(scope.type === "file"); 2170 const { file, project } = this.getFileAndProject(scope.args); 2171 const changes = project.getLanguageService().organizeImports({ type: "file", fileName: file }, this.getFormatOptions(file), this.getPreferences(file)); 2172 if (simplifiedResult) { 2173 return this.mapTextChangesToCodeEdits(changes); 2174 } 2175 else { 2176 return changes; 2177 } 2178 } 2179 2180 private getEditsForFileRename(args: protocol.GetEditsForFileRenameRequestArgs, simplifiedResult: boolean): readonly protocol.FileCodeEdits[] | readonly FileTextChanges[] { 2181 const oldPath = toNormalizedPath(args.oldFilePath); 2182 const newPath = toNormalizedPath(args.newFilePath); 2183 const formatOptions = this.getHostFormatOptions(); 2184 const preferences = this.getHostPreferences(); 2185 const changes = flattenCombineOutputResult( 2186 combineProjectOutputFromEveryProject( 2187 this.projectService, 2188 project => project.getLanguageService().getEditsForFileRename(oldPath, newPath, formatOptions, preferences), 2189 (a, b) => a.fileName === b.fileName 2190 ) 2191 ); 2192 return simplifiedResult ? changes.map(c => this.mapTextChangeToCodeEdit(c)) : changes; 2193 } 2194 2195 private getCodeFixes(args: protocol.CodeFixRequestArgs, simplifiedResult: boolean): readonly protocol.CodeFixAction[] | readonly CodeFixAction[] | undefined { 2196 const { file, project } = this.getFileAndProject(args); 2197 2198 const scriptInfo = project.getScriptInfoForNormalizedPath(file)!; 2199 const { startPosition, endPosition } = this.getStartAndEndPosition(args, scriptInfo); 2200 2201 const codeActions = project.getLanguageService().getCodeFixesAtPosition(file, startPosition, endPosition, args.errorCodes, this.getFormatOptions(file), this.getPreferences(file)); 2202 return simplifiedResult ? codeActions.map(codeAction => this.mapCodeFixAction(codeAction)) : codeActions; 2203 } 2204 2205 private getCombinedCodeFix({ scope, fixId }: protocol.GetCombinedCodeFixRequestArgs, simplifiedResult: boolean): protocol.CombinedCodeActions | CombinedCodeActions { 2206 Debug.assert(scope.type === "file"); 2207 const { file, project } = this.getFileAndProject(scope.args); 2208 const res = project.getLanguageService().getCombinedCodeFix({ type: "file", fileName: file }, fixId, this.getFormatOptions(file), this.getPreferences(file)); 2209 if (simplifiedResult) { 2210 return { changes: this.mapTextChangesToCodeEdits(res.changes), commands: res.commands }; 2211 } 2212 else { 2213 return res; 2214 } 2215 } 2216 2217 private applyCodeActionCommand(args: protocol.ApplyCodeActionCommandRequestArgs): {} { 2218 const commands = args.command as CodeActionCommand | CodeActionCommand[]; // They should be sending back the command we sent them. 2219 for (const command of toArray(commands)) { 2220 const { file, project } = this.getFileAndProject(command); 2221 project.getLanguageService().applyCodeActionCommand(command, this.getFormatOptions(file)).then( 2222 _result => { /* TODO: GH#20447 report success message? */ }, 2223 _error => { /* TODO: GH#20447 report errors */ }); 2224 } 2225 return {}; 2226 } 2227 2228 private getStartAndEndPosition(args: protocol.FileRangeRequestArgs, scriptInfo: ScriptInfo) { 2229 let startPosition: number | undefined, endPosition: number | undefined; 2230 if (args.startPosition !== undefined) { 2231 startPosition = args.startPosition; 2232 } 2233 else { 2234 startPosition = scriptInfo.lineOffsetToPosition(args.startLine, args.startOffset); 2235 // save the result so we don't always recompute 2236 args.startPosition = startPosition; 2237 } 2238 2239 if (args.endPosition !== undefined) { 2240 endPosition = args.endPosition; 2241 } 2242 else { 2243 endPosition = scriptInfo.lineOffsetToPosition(args.endLine, args.endOffset); 2244 args.endPosition = endPosition; 2245 } 2246 2247 return { startPosition, endPosition }; 2248 } 2249 2250 private mapCodeAction({ description, changes, commands }: CodeAction): protocol.CodeAction { 2251 return { description, changes: this.mapTextChangesToCodeEdits(changes), commands }; 2252 } 2253 2254 private mapCodeFixAction({ fixName, description, changes, commands, fixId, fixAllDescription }: CodeFixAction): protocol.CodeFixAction { 2255 return { fixName, description, changes: this.mapTextChangesToCodeEdits(changes), commands, fixId, fixAllDescription }; 2256 } 2257 2258 private mapTextChangesToCodeEdits(textChanges: readonly FileTextChanges[]): protocol.FileCodeEdits[] { 2259 return textChanges.map(change => this.mapTextChangeToCodeEdit(change)); 2260 } 2261 2262 private mapTextChangeToCodeEdit(textChanges: FileTextChanges): protocol.FileCodeEdits { 2263 const scriptInfo = this.projectService.getScriptInfoOrConfig(textChanges.fileName); 2264 if (!!textChanges.isNewFile === !!scriptInfo) { 2265 if (!scriptInfo) { // and !isNewFile 2266 this.projectService.logErrorForScriptInfoNotFound(textChanges.fileName); 2267 } 2268 Debug.fail("Expected isNewFile for (only) new files. " + JSON.stringify({ isNewFile: !!textChanges.isNewFile, hasScriptInfo: !!scriptInfo })); 2269 } 2270 return scriptInfo 2271 ? { fileName: textChanges.fileName, textChanges: textChanges.textChanges.map(textChange => convertTextChangeToCodeEdit(textChange, scriptInfo)) } 2272 : convertNewFileTextChangeToCodeEdit(textChanges); 2273 } 2274 2275 private convertTextChangeToCodeEdit(change: TextChange, scriptInfo: ScriptInfo): protocol.CodeEdit { 2276 return { 2277 start: scriptInfo.positionToLineOffset(change.span.start), 2278 end: scriptInfo.positionToLineOffset(change.span.start + change.span.length), 2279 newText: change.newText ? change.newText : "" 2280 }; 2281 } 2282 2283 private getBraceMatching(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): protocol.TextSpan[] | TextSpan[] | undefined { 2284 const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); 2285 const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; 2286 const position = this.getPosition(args, scriptInfo); 2287 2288 const spans = languageService.getBraceMatchingAtPosition(file, position); 2289 return !spans 2290 ? undefined 2291 : simplifiedResult 2292 ? spans.map(span => toProtocolTextSpan(span, scriptInfo)) 2293 : spans; 2294 } 2295 2296 private getDiagnosticsForProject(next: NextStep, delay: number, fileName: string): void { 2297 if (this.suppressDiagnosticEvents) { 2298 return; 2299 } 2300 2301 const { fileNames, languageServiceDisabled } = this.getProjectInfoWorker(fileName, /*projectFileName*/ undefined, /*needFileNameList*/ true, /*excludeConfigFiles*/ true); 2302 if (languageServiceDisabled) { 2303 return; 2304 } 2305 2306 // No need to analyze lib.d.ts 2307 const fileNamesInProject = fileNames!.filter(value => !stringContains(value, "lib.d.ts")); // TODO: GH#18217 2308 if (fileNamesInProject.length === 0) { 2309 return; 2310 } 2311 2312 // Sort the file name list to make the recently touched files come first 2313 const highPriorityFiles: NormalizedPath[] = []; 2314 const mediumPriorityFiles: NormalizedPath[] = []; 2315 const lowPriorityFiles: NormalizedPath[] = []; 2316 const veryLowPriorityFiles: NormalizedPath[] = []; 2317 const normalizedFileName = toNormalizedPath(fileName); 2318 const project = this.projectService.ensureDefaultProjectForFile(normalizedFileName); 2319 for (const fileNameInProject of fileNamesInProject) { 2320 if (this.getCanonicalFileName(fileNameInProject) === this.getCanonicalFileName(fileName)) { 2321 highPriorityFiles.push(fileNameInProject); 2322 } 2323 else { 2324 const info = this.projectService.getScriptInfo(fileNameInProject)!; // TODO: GH#18217 2325 if (!info.isScriptOpen()) { 2326 if (isDeclarationFileName(fileNameInProject)) { 2327 veryLowPriorityFiles.push(fileNameInProject); 2328 } 2329 else { 2330 lowPriorityFiles.push(fileNameInProject); 2331 } 2332 } 2333 else { 2334 mediumPriorityFiles.push(fileNameInProject); 2335 } 2336 } 2337 } 2338 2339 const sortedFiles = [...highPriorityFiles, ...mediumPriorityFiles, ...lowPriorityFiles, ...veryLowPriorityFiles]; 2340 const checkList = sortedFiles.map(fileName => ({ fileName, project })); 2341 // Project level error analysis runs on background files too, therefore 2342 // doesn't require the file to be opened 2343 this.updateErrorCheck(next, checkList, delay, /*requireOpen*/ false); 2344 } 2345 2346 private configurePlugin(args: protocol.ConfigurePluginRequestArguments) { 2347 this.projectService.configurePlugin(args); 2348 } 2349 2350 private getSmartSelectionRange(args: protocol.SelectionRangeRequestArgs, simplifiedResult: boolean) { 2351 const { locations } = args; 2352 const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); 2353 const scriptInfo = Debug.checkDefined(this.projectService.getScriptInfo(file)); 2354 2355 return map(locations, location => { 2356 const pos = this.getPosition(location, scriptInfo); 2357 const selectionRange = languageService.getSmartSelectionRange(file, pos); 2358 return simplifiedResult ? this.mapSelectionRange(selectionRange, scriptInfo) : selectionRange; 2359 }); 2360 } 2361 2362 private toggleLineComment(args: protocol.FileRangeRequestArgs, simplifiedResult: boolean): TextChange[] | protocol.CodeEdit[] { 2363 const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); 2364 const scriptInfo = this.projectService.getScriptInfo(file)!; 2365 const textRange = this.getRange(args, scriptInfo); 2366 2367 const textChanges = languageService.toggleLineComment(file, textRange); 2368 2369 if (simplifiedResult) { 2370 const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; 2371 2372 return textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, scriptInfo)); 2373 } 2374 2375 return textChanges; 2376 } 2377 2378 private toggleMultilineComment(args: protocol.FileRangeRequestArgs, simplifiedResult: boolean): TextChange[] | protocol.CodeEdit[] { 2379 const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); 2380 const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; 2381 const textRange = this.getRange(args, scriptInfo); 2382 2383 const textChanges = languageService.toggleMultilineComment(file, textRange); 2384 2385 if (simplifiedResult) { 2386 const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; 2387 2388 return textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, scriptInfo)); 2389 } 2390 2391 return textChanges; 2392 } 2393 2394 private commentSelection(args: protocol.FileRangeRequestArgs, simplifiedResult: boolean): TextChange[] | protocol.CodeEdit[] { 2395 const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); 2396 const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; 2397 const textRange = this.getRange(args, scriptInfo); 2398 2399 const textChanges = languageService.commentSelection(file, textRange); 2400 2401 if (simplifiedResult) { 2402 const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; 2403 2404 return textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, scriptInfo)); 2405 } 2406 2407 return textChanges; 2408 } 2409 2410 private uncommentSelection(args: protocol.FileRangeRequestArgs, simplifiedResult: boolean): TextChange[] | protocol.CodeEdit[] { 2411 const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); 2412 const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; 2413 const textRange = this.getRange(args, scriptInfo); 2414 2415 const textChanges = languageService.uncommentSelection(file, textRange); 2416 2417 if (simplifiedResult) { 2418 const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; 2419 2420 return textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, scriptInfo)); 2421 } 2422 2423 return textChanges; 2424 } 2425 2426 private mapSelectionRange(selectionRange: SelectionRange, scriptInfo: ScriptInfo): protocol.SelectionRange { 2427 const result: protocol.SelectionRange = { 2428 textSpan: toProtocolTextSpan(selectionRange.textSpan, scriptInfo), 2429 }; 2430 if (selectionRange.parent) { 2431 result.parent = this.mapSelectionRange(selectionRange.parent, scriptInfo); 2432 } 2433 return result; 2434 } 2435 2436 private getScriptInfoFromProjectService(file: string) { 2437 const normalizedFile = toNormalizedPath(file); 2438 const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(normalizedFile); 2439 if (!scriptInfo) { 2440 this.projectService.logErrorForScriptInfoNotFound(normalizedFile); 2441 return Errors.ThrowNoProject(); 2442 } 2443 return scriptInfo; 2444 } 2445 2446 private toProtocolCallHierarchyItem(item: CallHierarchyItem): protocol.CallHierarchyItem { 2447 const scriptInfo = this.getScriptInfoFromProjectService(item.file); 2448 return { 2449 name: item.name, 2450 kind: item.kind, 2451 kindModifiers: item.kindModifiers, 2452 file: item.file, 2453 containerName: item.containerName, 2454 span: toProtocolTextSpan(item.span, scriptInfo), 2455 selectionSpan: toProtocolTextSpan(item.selectionSpan, scriptInfo) 2456 }; 2457 } 2458 2459 private toProtocolCallHierarchyIncomingCall(incomingCall: CallHierarchyIncomingCall): protocol.CallHierarchyIncomingCall { 2460 const scriptInfo = this.getScriptInfoFromProjectService(incomingCall.from.file); 2461 return { 2462 from: this.toProtocolCallHierarchyItem(incomingCall.from), 2463 fromSpans: incomingCall.fromSpans.map(fromSpan => toProtocolTextSpan(fromSpan, scriptInfo)) 2464 }; 2465 } 2466 2467 private toProtocolCallHierarchyOutgoingCall(outgoingCall: CallHierarchyOutgoingCall, scriptInfo: ScriptInfo): protocol.CallHierarchyOutgoingCall { 2468 return { 2469 to: this.toProtocolCallHierarchyItem(outgoingCall.to), 2470 fromSpans: outgoingCall.fromSpans.map(fromSpan => toProtocolTextSpan(fromSpan, scriptInfo)) 2471 }; 2472 } 2473 2474 private prepareCallHierarchy(args: protocol.FileLocationRequestArgs): protocol.CallHierarchyItem | protocol.CallHierarchyItem[] | undefined { 2475 const { file, project } = this.getFileAndProject(args); 2476 const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file); 2477 if (scriptInfo) { 2478 const position = this.getPosition(args, scriptInfo); 2479 const result = project.getLanguageService().prepareCallHierarchy(file, position); 2480 return result && mapOneOrMany(result, item => this.toProtocolCallHierarchyItem(item)); 2481 } 2482 return undefined; 2483 } 2484 2485 private provideCallHierarchyIncomingCalls(args: protocol.FileLocationRequestArgs): protocol.CallHierarchyIncomingCall[] { 2486 const { file, project } = this.getFileAndProject(args); 2487 const scriptInfo = this.getScriptInfoFromProjectService(file); 2488 const incomingCalls = project.getLanguageService().provideCallHierarchyIncomingCalls(file, this.getPosition(args, scriptInfo)); 2489 return incomingCalls.map(call => this.toProtocolCallHierarchyIncomingCall(call)); 2490 } 2491 2492 private provideCallHierarchyOutgoingCalls(args: protocol.FileLocationRequestArgs): protocol.CallHierarchyOutgoingCall[] { 2493 const { file, project } = this.getFileAndProject(args); 2494 const scriptInfo = this.getScriptInfoFromProjectService(file); 2495 const outgoingCalls = project.getLanguageService().provideCallHierarchyOutgoingCalls(file, this.getPosition(args, scriptInfo)); 2496 return outgoingCalls.map(call => this.toProtocolCallHierarchyOutgoingCall(call, scriptInfo)); 2497 } 2498 2499 getCanonicalFileName(fileName: string) { 2500 const name = this.host.useCaseSensitiveFileNames ? fileName : toFileNameLowerCase(fileName); 2501 return normalizePath(name); 2502 } 2503 2504 exit() { /*overridden*/ } 2505 2506 private notRequired(): HandlerResponse { 2507 return { responseRequired: false }; 2508 } 2509 2510 private requiredResponse(response: {} | undefined): HandlerResponse { 2511 return { response, responseRequired: true }; 2512 } 2513 2514 private handlers = new Map(getEntries<(request: protocol.Request) => HandlerResponse>({ 2515 [CommandNames.Status]: () => { 2516 const response: protocol.StatusResponseBody = { version: ts.version }; // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier 2517 return this.requiredResponse(response); 2518 }, 2519 [CommandNames.OpenExternalProject]: (request: protocol.OpenExternalProjectRequest) => { 2520 this.projectService.openExternalProject(request.arguments); 2521 // TODO: GH#20447 report errors 2522 return this.requiredResponse(/*response*/ true); 2523 }, 2524 [CommandNames.OpenExternalProjects]: (request: protocol.OpenExternalProjectsRequest) => { 2525 this.projectService.openExternalProjects(request.arguments.projects); 2526 // TODO: GH#20447 report errors 2527 return this.requiredResponse(/*response*/ true); 2528 }, 2529 [CommandNames.CloseExternalProject]: (request: protocol.CloseExternalProjectRequest) => { 2530 this.projectService.closeExternalProject(request.arguments.projectFileName); 2531 // TODO: GH#20447 report errors 2532 return this.requiredResponse(/*response*/ true); 2533 }, 2534 [CommandNames.SynchronizeProjectList]: (request: protocol.SynchronizeProjectListRequest) => { 2535 const result = this.projectService.synchronizeProjectList(request.arguments.knownProjects, request.arguments.includeProjectReferenceRedirectInfo); 2536 if (!result.some(p => p.projectErrors && p.projectErrors.length !== 0)) { 2537 return this.requiredResponse(result); 2538 } 2539 const converted = map(result, p => { 2540 if (!p.projectErrors || p.projectErrors.length === 0) { 2541 return p; 2542 } 2543 return { 2544 info: p.info, 2545 changes: p.changes, 2546 files: p.files, 2547 projectErrors: this.convertToDiagnosticsWithLinePosition(p.projectErrors, /*scriptInfo*/ undefined) 2548 }; 2549 }); 2550 return this.requiredResponse(converted); 2551 }, 2552 [CommandNames.UpdateOpen]: (request: protocol.UpdateOpenRequest) => { 2553 this.changeSeq++; 2554 this.projectService.applyChangesInOpenFiles( 2555 request.arguments.openFiles && mapIterator(arrayIterator(request.arguments.openFiles), file => ({ 2556 fileName: file.file, 2557 content: file.fileContent, 2558 scriptKind: file.scriptKindName, 2559 projectRootPath: file.projectRootPath 2560 })), 2561 request.arguments.changedFiles && mapIterator(arrayIterator(request.arguments.changedFiles), file => ({ 2562 fileName: file.fileName, 2563 changes: mapDefinedIterator(arrayReverseIterator(file.textChanges), change => { 2564 const scriptInfo = Debug.checkDefined(this.projectService.getScriptInfo(file.fileName)); 2565 const start = scriptInfo.lineOffsetToPosition(change.start.line, change.start.offset); 2566 const end = scriptInfo.lineOffsetToPosition(change.end.line, change.end.offset); 2567 return start >= 0 ? { span: { start, length: end - start }, newText: change.newText } : undefined; 2568 }) 2569 })), 2570 request.arguments.closedFiles 2571 ); 2572 return this.requiredResponse(/*response*/ true); 2573 }, 2574 [CommandNames.ApplyChangedToOpenFiles]: (request: protocol.ApplyChangedToOpenFilesRequest) => { 2575 this.changeSeq++; 2576 this.projectService.applyChangesInOpenFiles( 2577 request.arguments.openFiles && arrayIterator(request.arguments.openFiles), 2578 request.arguments.changedFiles && mapIterator(arrayIterator(request.arguments.changedFiles), file => ({ 2579 fileName: file.fileName, 2580 // apply changes in reverse order 2581 changes: arrayReverseIterator(file.changes) 2582 })), 2583 request.arguments.closedFiles 2584 ); 2585 // TODO: report errors 2586 return this.requiredResponse(/*response*/ true); 2587 }, 2588 [CommandNames.Exit]: () => { 2589 this.exit(); 2590 return this.notRequired(); 2591 }, 2592 [CommandNames.Definition]: (request: protocol.DefinitionRequest) => { 2593 return this.requiredResponse(this.getDefinition(request.arguments, /*simplifiedResult*/ true)); 2594 }, 2595 [CommandNames.DefinitionFull]: (request: protocol.DefinitionRequest) => { 2596 return this.requiredResponse(this.getDefinition(request.arguments, /*simplifiedResult*/ false)); 2597 }, 2598 [CommandNames.DefinitionAndBoundSpan]: (request: protocol.DefinitionRequest) => { 2599 return this.requiredResponse(this.getDefinitionAndBoundSpan(request.arguments, /*simplifiedResult*/ true)); 2600 }, 2601 [CommandNames.DefinitionAndBoundSpanFull]: (request: protocol.DefinitionRequest) => { 2602 return this.requiredResponse(this.getDefinitionAndBoundSpan(request.arguments, /*simplifiedResult*/ false)); 2603 }, 2604 [CommandNames.EmitOutput]: (request: protocol.EmitOutputRequest) => { 2605 return this.requiredResponse(this.getEmitOutput(request.arguments)); 2606 }, 2607 [CommandNames.TypeDefinition]: (request: protocol.FileLocationRequest) => { 2608 return this.requiredResponse(this.getTypeDefinition(request.arguments)); 2609 }, 2610 [CommandNames.Implementation]: (request: protocol.Request) => { 2611 return this.requiredResponse(this.getImplementation(request.arguments, /*simplifiedResult*/ true)); 2612 }, 2613 [CommandNames.ImplementationFull]: (request: protocol.Request) => { 2614 return this.requiredResponse(this.getImplementation(request.arguments, /*simplifiedResult*/ false)); 2615 }, 2616 [CommandNames.References]: (request: protocol.FileLocationRequest) => { 2617 return this.requiredResponse(this.getReferences(request.arguments, /*simplifiedResult*/ true)); 2618 }, 2619 [CommandNames.ReferencesFull]: (request: protocol.FileLocationRequest) => { 2620 return this.requiredResponse(this.getReferences(request.arguments, /*simplifiedResult*/ false)); 2621 }, 2622 [CommandNames.Rename]: (request: protocol.RenameRequest) => { 2623 return this.requiredResponse(this.getRenameLocations(request.arguments, /*simplifiedResult*/ true)); 2624 }, 2625 [CommandNames.RenameLocationsFull]: (request: protocol.RenameFullRequest) => { 2626 return this.requiredResponse(this.getRenameLocations(request.arguments, /*simplifiedResult*/ false)); 2627 }, 2628 [CommandNames.RenameInfoFull]: (request: protocol.FileLocationRequest) => { 2629 return this.requiredResponse(this.getRenameInfo(request.arguments)); 2630 }, 2631 [CommandNames.Open]: (request: protocol.OpenRequest) => { 2632 this.openClientFile( 2633 toNormalizedPath(request.arguments.file), 2634 request.arguments.fileContent, 2635 convertScriptKindName(request.arguments.scriptKindName!), // TODO: GH#18217 2636 request.arguments.projectRootPath ? toNormalizedPath(request.arguments.projectRootPath) : undefined); 2637 return this.notRequired(); 2638 }, 2639 [CommandNames.Quickinfo]: (request: protocol.QuickInfoRequest) => { 2640 return this.requiredResponse(this.getQuickInfoWorker(request.arguments, /*simplifiedResult*/ true)); 2641 }, 2642 [CommandNames.QuickinfoFull]: (request: protocol.QuickInfoRequest) => { 2643 return this.requiredResponse(this.getQuickInfoWorker(request.arguments, /*simplifiedResult*/ false)); 2644 }, 2645 [CommandNames.GetOutliningSpans]: (request: protocol.FileRequest) => { 2646 return this.requiredResponse(this.getOutliningSpans(request.arguments, /*simplifiedResult*/ true)); 2647 }, 2648 [CommandNames.GetOutliningSpansFull]: (request: protocol.FileRequest) => { 2649 return this.requiredResponse(this.getOutliningSpans(request.arguments, /*simplifiedResult*/ false)); 2650 }, 2651 [CommandNames.TodoComments]: (request: protocol.TodoCommentRequest) => { 2652 return this.requiredResponse(this.getTodoComments(request.arguments)); 2653 }, 2654 [CommandNames.Indentation]: (request: protocol.IndentationRequest) => { 2655 return this.requiredResponse(this.getIndentation(request.arguments)); 2656 }, 2657 [CommandNames.NameOrDottedNameSpan]: (request: protocol.FileLocationRequest) => { 2658 return this.requiredResponse(this.getNameOrDottedNameSpan(request.arguments)); 2659 }, 2660 [CommandNames.BreakpointStatement]: (request: protocol.FileLocationRequest) => { 2661 return this.requiredResponse(this.getBreakpointStatement(request.arguments)); 2662 }, 2663 [CommandNames.BraceCompletion]: (request: protocol.BraceCompletionRequest) => { 2664 return this.requiredResponse(this.isValidBraceCompletion(request.arguments)); 2665 }, 2666 [CommandNames.DocCommentTemplate]: (request: protocol.DocCommentTemplateRequest) => { 2667 return this.requiredResponse(this.getDocCommentTemplate(request.arguments)); 2668 }, 2669 [CommandNames.GetSpanOfEnclosingComment]: (request: protocol.SpanOfEnclosingCommentRequest) => { 2670 return this.requiredResponse(this.getSpanOfEnclosingComment(request.arguments)); 2671 }, 2672 [CommandNames.FileReferences]: (request: protocol.FileReferencesRequest) => { 2673 return this.requiredResponse(this.getFileReferences(request.arguments, /*simplifiedResult*/ true)); 2674 }, 2675 [CommandNames.FileReferencesFull]: (request: protocol.FileReferencesRequest) => { 2676 return this.requiredResponse(this.getFileReferences(request.arguments, /*simplifiedResult*/ false)); 2677 }, 2678 [CommandNames.Format]: (request: protocol.FormatRequest) => { 2679 return this.requiredResponse(this.getFormattingEditsForRange(request.arguments)); 2680 }, 2681 [CommandNames.Formatonkey]: (request: protocol.FormatOnKeyRequest) => { 2682 return this.requiredResponse(this.getFormattingEditsAfterKeystroke(request.arguments)); 2683 }, 2684 [CommandNames.FormatFull]: (request: protocol.FormatRequest) => { 2685 return this.requiredResponse(this.getFormattingEditsForDocumentFull(request.arguments)); 2686 }, 2687 [CommandNames.FormatonkeyFull]: (request: protocol.FormatOnKeyRequest) => { 2688 return this.requiredResponse(this.getFormattingEditsAfterKeystrokeFull(request.arguments)); 2689 }, 2690 [CommandNames.FormatRangeFull]: (request: protocol.FormatRequest) => { 2691 return this.requiredResponse(this.getFormattingEditsForRangeFull(request.arguments)); 2692 }, 2693 [CommandNames.CompletionInfo]: (request: protocol.CompletionsRequest) => { 2694 return this.requiredResponse(this.getCompletions(request.arguments, CommandNames.CompletionInfo)); 2695 }, 2696 [CommandNames.Completions]: (request: protocol.CompletionsRequest) => { 2697 return this.requiredResponse(this.getCompletions(request.arguments, CommandNames.Completions)); 2698 }, 2699 [CommandNames.CompletionsFull]: (request: protocol.CompletionsRequest) => { 2700 return this.requiredResponse(this.getCompletions(request.arguments, CommandNames.CompletionsFull)); 2701 }, 2702 [CommandNames.CompletionDetails]: (request: protocol.CompletionDetailsRequest) => { 2703 return this.requiredResponse(this.getCompletionEntryDetails(request.arguments, /*simplifiedResult*/ true)); 2704 }, 2705 [CommandNames.CompletionDetailsFull]: (request: protocol.CompletionDetailsRequest) => { 2706 return this.requiredResponse(this.getCompletionEntryDetails(request.arguments, /*simplifiedResult*/ false)); 2707 }, 2708 [CommandNames.CompileOnSaveAffectedFileList]: (request: protocol.CompileOnSaveAffectedFileListRequest) => { 2709 return this.requiredResponse(this.getCompileOnSaveAffectedFileList(request.arguments)); 2710 }, 2711 [CommandNames.CompileOnSaveEmitFile]: (request: protocol.CompileOnSaveEmitFileRequest) => { 2712 return this.requiredResponse(this.emitFile(request.arguments)); 2713 }, 2714 [CommandNames.SignatureHelp]: (request: protocol.SignatureHelpRequest) => { 2715 return this.requiredResponse(this.getSignatureHelpItems(request.arguments, /*simplifiedResult*/ true)); 2716 }, 2717 [CommandNames.SignatureHelpFull]: (request: protocol.SignatureHelpRequest) => { 2718 return this.requiredResponse(this.getSignatureHelpItems(request.arguments, /*simplifiedResult*/ false)); 2719 }, 2720 [CommandNames.CompilerOptionsDiagnosticsFull]: (request: protocol.CompilerOptionsDiagnosticsRequest) => { 2721 return this.requiredResponse(this.getCompilerOptionsDiagnostics(request.arguments)); 2722 }, 2723 [CommandNames.EncodedSyntacticClassificationsFull]: (request: protocol.EncodedSyntacticClassificationsRequest) => { 2724 return this.requiredResponse(this.getEncodedSyntacticClassifications(request.arguments)); 2725 }, 2726 [CommandNames.EncodedSemanticClassificationsFull]: (request: protocol.EncodedSemanticClassificationsRequest) => { 2727 return this.requiredResponse(this.getEncodedSemanticClassifications(request.arguments)); 2728 }, 2729 [CommandNames.Cleanup]: () => { 2730 this.cleanup(); 2731 return this.requiredResponse(/*response*/ true); 2732 }, 2733 [CommandNames.SemanticDiagnosticsSync]: (request: protocol.SemanticDiagnosticsSyncRequest) => { 2734 return this.requiredResponse(this.getSemanticDiagnosticsSync(request.arguments)); 2735 }, 2736 [CommandNames.SyntacticDiagnosticsSync]: (request: protocol.SyntacticDiagnosticsSyncRequest) => { 2737 return this.requiredResponse(this.getSyntacticDiagnosticsSync(request.arguments)); 2738 }, 2739 [CommandNames.SuggestionDiagnosticsSync]: (request: protocol.SuggestionDiagnosticsSyncRequest) => { 2740 return this.requiredResponse(this.getSuggestionDiagnosticsSync(request.arguments)); 2741 }, 2742 [CommandNames.Geterr]: (request: protocol.GeterrRequest) => { 2743 this.errorCheck.startNew(next => this.getDiagnostics(next, request.arguments.delay, request.arguments.files)); 2744 return this.notRequired(); 2745 }, 2746 [CommandNames.GeterrForProject]: (request: protocol.GeterrForProjectRequest) => { 2747 this.errorCheck.startNew(next => this.getDiagnosticsForProject(next, request.arguments.delay, request.arguments.file)); 2748 return this.notRequired(); 2749 }, 2750 [CommandNames.Change]: (request: protocol.ChangeRequest) => { 2751 this.change(request.arguments); 2752 return this.notRequired(); 2753 }, 2754 [CommandNames.Configure]: (request: protocol.ConfigureRequest) => { 2755 this.projectService.setHostConfiguration(request.arguments); 2756 this.doOutput(/*info*/ undefined, CommandNames.Configure, request.seq, /*success*/ true); 2757 return this.notRequired(); 2758 }, 2759 [CommandNames.Reload]: (request: protocol.ReloadRequest) => { 2760 this.reload(request.arguments, request.seq); 2761 return this.requiredResponse({ reloadFinished: true }); 2762 }, 2763 [CommandNames.Saveto]: (request: protocol.Request) => { 2764 const savetoArgs = <protocol.SavetoRequestArgs>request.arguments; 2765 this.saveToTmp(savetoArgs.file, savetoArgs.tmpfile); 2766 return this.notRequired(); 2767 }, 2768 [CommandNames.Close]: (request: protocol.Request) => { 2769 const closeArgs = <protocol.FileRequestArgs>request.arguments; 2770 this.closeClientFile(closeArgs.file); 2771 return this.notRequired(); 2772 }, 2773 [CommandNames.Navto]: (request: protocol.NavtoRequest) => { 2774 return this.requiredResponse(this.getNavigateToItems(request.arguments, /*simplifiedResult*/ true)); 2775 }, 2776 [CommandNames.NavtoFull]: (request: protocol.NavtoRequest) => { 2777 return this.requiredResponse(this.getNavigateToItems(request.arguments, /*simplifiedResult*/ false)); 2778 }, 2779 [CommandNames.Brace]: (request: protocol.FileLocationRequest) => { 2780 return this.requiredResponse(this.getBraceMatching(request.arguments, /*simplifiedResult*/ true)); 2781 }, 2782 [CommandNames.BraceFull]: (request: protocol.FileLocationRequest) => { 2783 return this.requiredResponse(this.getBraceMatching(request.arguments, /*simplifiedResult*/ false)); 2784 }, 2785 [CommandNames.NavBar]: (request: protocol.FileRequest) => { 2786 return this.requiredResponse(this.getNavigationBarItems(request.arguments, /*simplifiedResult*/ true)); 2787 }, 2788 [CommandNames.NavBarFull]: (request: protocol.FileRequest) => { 2789 return this.requiredResponse(this.getNavigationBarItems(request.arguments, /*simplifiedResult*/ false)); 2790 }, 2791 [CommandNames.NavTree]: (request: protocol.FileRequest) => { 2792 return this.requiredResponse(this.getNavigationTree(request.arguments, /*simplifiedResult*/ true)); 2793 }, 2794 [CommandNames.NavTreeFull]: (request: protocol.FileRequest) => { 2795 return this.requiredResponse(this.getNavigationTree(request.arguments, /*simplifiedResult*/ false)); 2796 }, 2797 [CommandNames.Occurrences]: (request: protocol.FileLocationRequest) => { 2798 return this.requiredResponse(this.getOccurrences(request.arguments)); 2799 }, 2800 [CommandNames.DocumentHighlights]: (request: protocol.DocumentHighlightsRequest) => { 2801 return this.requiredResponse(this.getDocumentHighlights(request.arguments, /*simplifiedResult*/ true)); 2802 }, 2803 [CommandNames.DocumentHighlightsFull]: (request: protocol.DocumentHighlightsRequest) => { 2804 return this.requiredResponse(this.getDocumentHighlights(request.arguments, /*simplifiedResult*/ false)); 2805 }, 2806 [CommandNames.CompilerOptionsForInferredProjects]: (request: protocol.SetCompilerOptionsForInferredProjectsRequest) => { 2807 this.setCompilerOptionsForInferredProjects(request.arguments); 2808 return this.requiredResponse(/*response*/ true); 2809 }, 2810 [CommandNames.ProjectInfo]: (request: protocol.ProjectInfoRequest) => { 2811 return this.requiredResponse(this.getProjectInfo(request.arguments)); 2812 }, 2813 [CommandNames.ReloadProjects]: () => { 2814 this.projectService.reloadProjects(); 2815 return this.notRequired(); 2816 }, 2817 [CommandNames.JsxClosingTag]: (request: protocol.JsxClosingTagRequest) => { 2818 return this.requiredResponse(this.getJsxClosingTag(request.arguments)); 2819 }, 2820 [CommandNames.GetCodeFixes]: (request: protocol.CodeFixRequest) => { 2821 return this.requiredResponse(this.getCodeFixes(request.arguments, /*simplifiedResult*/ true)); 2822 }, 2823 [CommandNames.GetCodeFixesFull]: (request: protocol.CodeFixRequest) => { 2824 return this.requiredResponse(this.getCodeFixes(request.arguments, /*simplifiedResult*/ false)); 2825 }, 2826 [CommandNames.GetCombinedCodeFix]: (request: protocol.GetCombinedCodeFixRequest) => { 2827 return this.requiredResponse(this.getCombinedCodeFix(request.arguments, /*simplifiedResult*/ true)); 2828 }, 2829 [CommandNames.GetCombinedCodeFixFull]: (request: protocol.GetCombinedCodeFixRequest) => { 2830 return this.requiredResponse(this.getCombinedCodeFix(request.arguments, /*simplifiedResult*/ false)); 2831 }, 2832 [CommandNames.ApplyCodeActionCommand]: (request: protocol.ApplyCodeActionCommandRequest) => { 2833 return this.requiredResponse(this.applyCodeActionCommand(request.arguments)); 2834 }, 2835 [CommandNames.GetSupportedCodeFixes]: () => { 2836 return this.requiredResponse(this.getSupportedCodeFixes()); 2837 }, 2838 [CommandNames.GetApplicableRefactors]: (request: protocol.GetApplicableRefactorsRequest) => { 2839 return this.requiredResponse(this.getApplicableRefactors(request.arguments)); 2840 }, 2841 [CommandNames.GetEditsForRefactor]: (request: protocol.GetEditsForRefactorRequest) => { 2842 return this.requiredResponse(this.getEditsForRefactor(request.arguments, /*simplifiedResult*/ true)); 2843 }, 2844 [CommandNames.GetEditsForRefactorFull]: (request: protocol.GetEditsForRefactorRequest) => { 2845 return this.requiredResponse(this.getEditsForRefactor(request.arguments, /*simplifiedResult*/ false)); 2846 }, 2847 [CommandNames.OrganizeImports]: (request: protocol.OrganizeImportsRequest) => { 2848 return this.requiredResponse(this.organizeImports(request.arguments, /*simplifiedResult*/ true)); 2849 }, 2850 [CommandNames.OrganizeImportsFull]: (request: protocol.OrganizeImportsRequest) => { 2851 return this.requiredResponse(this.organizeImports(request.arguments, /*simplifiedResult*/ false)); 2852 }, 2853 [CommandNames.GetEditsForFileRename]: (request: protocol.GetEditsForFileRenameRequest) => { 2854 return this.requiredResponse(this.getEditsForFileRename(request.arguments, /*simplifiedResult*/ true)); 2855 }, 2856 [CommandNames.GetEditsForFileRenameFull]: (request: protocol.GetEditsForFileRenameRequest) => { 2857 return this.requiredResponse(this.getEditsForFileRename(request.arguments, /*simplifiedResult*/ false)); 2858 }, 2859 [CommandNames.ConfigurePlugin]: (request: protocol.ConfigurePluginRequest) => { 2860 this.configurePlugin(request.arguments); 2861 this.doOutput(/*info*/ undefined, CommandNames.ConfigurePlugin, request.seq, /*success*/ true); 2862 return this.notRequired(); 2863 }, 2864 [CommandNames.SelectionRange]: (request: protocol.SelectionRangeRequest) => { 2865 return this.requiredResponse(this.getSmartSelectionRange(request.arguments, /*simplifiedResult*/ true)); 2866 }, 2867 [CommandNames.SelectionRangeFull]: (request: protocol.SelectionRangeRequest) => { 2868 return this.requiredResponse(this.getSmartSelectionRange(request.arguments, /*simplifiedResult*/ false)); 2869 }, 2870 [CommandNames.PrepareCallHierarchy]: (request: protocol.PrepareCallHierarchyRequest) => { 2871 return this.requiredResponse(this.prepareCallHierarchy(request.arguments)); 2872 }, 2873 [CommandNames.ProvideCallHierarchyIncomingCalls]: (request: protocol.ProvideCallHierarchyIncomingCallsRequest) => { 2874 return this.requiredResponse(this.provideCallHierarchyIncomingCalls(request.arguments)); 2875 }, 2876 [CommandNames.ProvideCallHierarchyOutgoingCalls]: (request: protocol.ProvideCallHierarchyOutgoingCallsRequest) => { 2877 return this.requiredResponse(this.provideCallHierarchyOutgoingCalls(request.arguments)); 2878 }, 2879 [CommandNames.ToggleLineComment]: (request: protocol.ToggleLineCommentRequest) => { 2880 return this.requiredResponse(this.toggleLineComment(request.arguments, /*simplifiedResult*/ true)); 2881 }, 2882 [CommandNames.ToggleLineCommentFull]: (request: protocol.ToggleLineCommentRequest) => { 2883 return this.requiredResponse(this.toggleLineComment(request.arguments, /*simplifiedResult*/ false)); 2884 }, 2885 [CommandNames.ToggleMultilineComment]: (request: protocol.ToggleMultilineCommentRequest) => { 2886 return this.requiredResponse(this.toggleMultilineComment(request.arguments, /*simplifiedResult*/ true)); 2887 }, 2888 [CommandNames.ToggleMultilineCommentFull]: (request: protocol.ToggleMultilineCommentRequest) => { 2889 return this.requiredResponse(this.toggleMultilineComment(request.arguments, /*simplifiedResult*/ false)); 2890 }, 2891 [CommandNames.CommentSelection]: (request: protocol.CommentSelectionRequest) => { 2892 return this.requiredResponse(this.commentSelection(request.arguments, /*simplifiedResult*/ true)); 2893 }, 2894 [CommandNames.CommentSelectionFull]: (request: protocol.CommentSelectionRequest) => { 2895 return this.requiredResponse(this.commentSelection(request.arguments, /*simplifiedResult*/ false)); 2896 }, 2897 [CommandNames.UncommentSelection]: (request: protocol.UncommentSelectionRequest) => { 2898 return this.requiredResponse(this.uncommentSelection(request.arguments, /*simplifiedResult*/ true)); 2899 }, 2900 [CommandNames.UncommentSelectionFull]: (request: protocol.UncommentSelectionRequest) => { 2901 return this.requiredResponse(this.uncommentSelection(request.arguments, /*simplifiedResult*/ false)); 2902 }, 2903 })); 2904 2905 public addProtocolHandler(command: string, handler: (request: protocol.Request) => HandlerResponse) { 2906 if (this.handlers.has(command)) { 2907 throw new Error(`Protocol handler already exists for command "${command}"`); 2908 } 2909 this.handlers.set(command, handler); 2910 } 2911 2912 private setCurrentRequest(requestId: number): void { 2913 Debug.assert(this.currentRequestId === undefined); 2914 this.currentRequestId = requestId; 2915 this.cancellationToken.setRequest(requestId); 2916 } 2917 2918 private resetCurrentRequest(requestId: number): void { 2919 Debug.assert(this.currentRequestId === requestId); 2920 this.currentRequestId = undefined!; // TODO: GH#18217 2921 this.cancellationToken.resetRequest(requestId); 2922 } 2923 2924 public executeWithRequestId<T>(requestId: number, f: () => T) { 2925 try { 2926 this.setCurrentRequest(requestId); 2927 return f(); 2928 } 2929 finally { 2930 this.resetCurrentRequest(requestId); 2931 } 2932 } 2933 2934 public executeCommand(request: protocol.Request): HandlerResponse { 2935 const handler = this.handlers.get(request.command); 2936 if (handler) { 2937 return this.executeWithRequestId(request.seq, () => handler(request)); 2938 } 2939 else { 2940 this.logger.msg(`Unrecognized JSON command:${stringifyIndented(request)}`, Msg.Err); 2941 this.doOutput(/*info*/ undefined, CommandNames.Unknown, request.seq, /*success*/ false, `Unrecognized JSON command: ${request.command}`); 2942 return { responseRequired: false }; 2943 } 2944 } 2945 2946 public onMessage(message: TMessage) { 2947 this.gcTimer.scheduleCollect(); 2948 2949 this.performanceData = undefined; 2950 2951 let start: number[] | undefined; 2952 if (this.logger.hasLevel(LogLevel.requestTime)) { 2953 start = this.hrtime(); 2954 if (this.logger.hasLevel(LogLevel.verbose)) { 2955 this.logger.info(`request:${indent(this.toStringMessage(message))}`); 2956 } 2957 } 2958 2959 let request: protocol.Request | undefined; 2960 let relevantFile: protocol.FileRequestArgs | undefined; 2961 try { 2962 request = this.parseMessage(message); 2963 relevantFile = request.arguments && (request as protocol.FileRequest).arguments.file ? (request as protocol.FileRequest).arguments : undefined; 2964 2965 tracing?.instant(tracing.Phase.Session, "request", { seq: request.seq, command: request.command }); 2966 perfLogger.logStartCommand("" + request.command, this.toStringMessage(message).substring(0, 100)); 2967 2968 tracing?.push(tracing.Phase.Session, "executeCommand", { seq: request.seq, command: request.command }, /*separateBeginAndEnd*/ true); 2969 const { response, responseRequired } = this.executeCommand(request); 2970 tracing?.pop(); 2971 2972 if (this.logger.hasLevel(LogLevel.requestTime)) { 2973 const elapsedTime = hrTimeToMilliseconds(this.hrtime(start)).toFixed(4); 2974 if (responseRequired) { 2975 this.logger.perftrc(`${request.seq}::${request.command}: elapsed time (in milliseconds) ${elapsedTime}`); 2976 } 2977 else { 2978 this.logger.perftrc(`${request.seq}::${request.command}: async elapsed time (in milliseconds) ${elapsedTime}`); 2979 } 2980 } 2981 2982 // Note: Log before writing the response, else the editor can complete its activity before the server does 2983 perfLogger.logStopCommand("" + request.command, "Success"); 2984 tracing?.instant(tracing.Phase.Session, "response", { seq: request.seq, command: request.command, success: !!response }); 2985 if (response) { 2986 this.doOutput(response, request.command, request.seq, /*success*/ true); 2987 } 2988 else if (responseRequired) { 2989 this.doOutput(/*info*/ undefined, request.command, request.seq, /*success*/ false, "No content available."); 2990 } 2991 } 2992 catch (err) { 2993 // Cancellation or an error may have left incomplete events on the tracing stack. 2994 tracing?.popAll(); 2995 2996 if (err instanceof OperationCanceledException) { 2997 // Handle cancellation exceptions 2998 perfLogger.logStopCommand("" + (request && request.command), "Canceled: " + err); 2999 tracing?.instant(tracing.Phase.Session, "commandCanceled", { seq: request?.seq, command: request?.command }); 3000 this.doOutput({ canceled: true }, request!.command, request!.seq, /*success*/ true); 3001 return; 3002 } 3003 3004 this.logErrorWorker(err, this.toStringMessage(message), relevantFile); 3005 perfLogger.logStopCommand("" + (request && request.command), "Error: " + err); 3006 tracing?.instant(tracing.Phase.Session, "commandError", { seq: request?.seq, command: request?.command, message: (<Error>err).message }); 3007 3008 this.doOutput( 3009 /*info*/ undefined, 3010 request ? request.command : CommandNames.Unknown, 3011 request ? request.seq : 0, 3012 /*success*/ false, 3013 "Error processing request. " + (<StackTraceError>err).message + "\n" + (<StackTraceError>err).stack); 3014 } 3015 } 3016 3017 protected parseMessage(message: TMessage): protocol.Request { 3018 return <protocol.Request>JSON.parse(message as any as string); 3019 } 3020 3021 protected toStringMessage(message: TMessage): string { 3022 return message as any as string; 3023 } 3024 3025 private getFormatOptions(file: NormalizedPath): FormatCodeSettings { 3026 return this.projectService.getFormatCodeOptions(file); 3027 } 3028 3029 private getPreferences(file: NormalizedPath): protocol.UserPreferences { 3030 return this.projectService.getPreferences(file); 3031 } 3032 3033 private getHostFormatOptions(): FormatCodeSettings { 3034 return this.projectService.getHostFormatCodeOptions(); 3035 } 3036 3037 private getHostPreferences(): protocol.UserPreferences { 3038 return this.projectService.getHostPreferences(); 3039 } 3040 } 3041 3042 interface FileAndProject { 3043 readonly file: NormalizedPath; 3044 readonly project: Project; 3045 } 3046 3047 function toProtocolTextSpan(textSpan: TextSpan, scriptInfo: ScriptInfo): protocol.TextSpan { 3048 return { 3049 start: scriptInfo.positionToLineOffset(textSpan.start), 3050 end: scriptInfo.positionToLineOffset(textSpanEnd(textSpan)) 3051 }; 3052 } 3053 3054 function toProtocolTextSpanWithContext(span: TextSpan, contextSpan: TextSpan | undefined, scriptInfo: ScriptInfo): protocol.TextSpanWithContext { 3055 const textSpan = toProtocolTextSpan(span, scriptInfo); 3056 const contextTextSpan = contextSpan && toProtocolTextSpan(contextSpan, scriptInfo); 3057 return contextTextSpan ? 3058 { ...textSpan, contextStart: contextTextSpan.start, contextEnd: contextTextSpan.end } : 3059 textSpan; 3060 } 3061 3062 function convertTextChangeToCodeEdit(change: TextChange, scriptInfo: ScriptInfoOrConfig): protocol.CodeEdit { 3063 return { start: positionToLineOffset(scriptInfo, change.span.start), end: positionToLineOffset(scriptInfo, textSpanEnd(change.span)), newText: change.newText }; 3064 } 3065 3066 function positionToLineOffset(info: ScriptInfoOrConfig, position: number): protocol.Location { 3067 return isConfigFile(info) ? locationFromLineAndCharacter(info.getLineAndCharacterOfPosition(position)) : info.positionToLineOffset(position); 3068 } 3069 3070 function locationFromLineAndCharacter(lc: LineAndCharacter): protocol.Location { 3071 return { line: lc.line + 1, offset: lc.character + 1 }; 3072 } 3073 3074 function convertNewFileTextChangeToCodeEdit(textChanges: FileTextChanges): protocol.FileCodeEdits { 3075 Debug.assert(textChanges.textChanges.length === 1); 3076 const change = first(textChanges.textChanges); 3077 Debug.assert(change.span.start === 0 && change.span.length === 0); 3078 return { fileName: textChanges.fileName, textChanges: [{ start: { line: 0, offset: 0 }, end: { line: 0, offset: 0 }, newText: change.newText }] }; 3079 } 3080 3081 export interface HandlerResponse { 3082 response?: {}; 3083 responseRequired?: boolean; 3084 } 3085 3086 /* @internal */ // Exported only for tests 3087 export function getLocationInNewDocument(oldText: string, renameFilename: string, renameLocation: number, edits: readonly FileTextChanges[]): protocol.Location { 3088 const newText = applyEdits(oldText, renameFilename, edits); 3089 const { line, character } = computeLineAndCharacterOfPosition(computeLineStarts(newText), renameLocation); 3090 return { line: line + 1, offset: character + 1 }; 3091 } 3092 3093 function applyEdits(text: string, textFilename: string, edits: readonly FileTextChanges[]): string { 3094 for (const { fileName, textChanges } of edits) { 3095 if (fileName !== textFilename) { 3096 continue; 3097 } 3098 3099 for (let i = textChanges.length - 1; i >= 0; i--) { 3100 const { newText, span: { start, length } } = textChanges[i]; 3101 text = text.slice(0, start) + newText + text.slice(start + length); 3102 } 3103 } 3104 3105 return text; 3106 } 3107 3108 function referenceEntryToReferencesResponseItem(projectService: ProjectService, { fileName, textSpan, contextSpan, isWriteAccess, isDefinition }: ReferenceEntry): protocol.ReferencesResponseItem { 3109 const scriptInfo = Debug.checkDefined(projectService.getScriptInfo(fileName)); 3110 const span = toProtocolTextSpanWithContext(textSpan, contextSpan, scriptInfo); 3111 const lineSpan = scriptInfo.lineToTextSpan(span.start.line - 1); 3112 const lineText = scriptInfo.getSnapshot().getText(lineSpan.start, textSpanEnd(lineSpan)).replace(/\r|\n/g, ""); 3113 return { 3114 file: fileName, 3115 ...span, 3116 lineText, 3117 isWriteAccess, 3118 isDefinition 3119 }; 3120 } 3121} 3122