• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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