• 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 = (protocol as any).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: (e as Error).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    interface ProjectNavigateToItems {
298        project: Project;
299        navigateToItems: readonly NavigateToItem[];
300    }
301
302    function createDocumentSpanSet(): Set<DocumentSpan> {
303        return createSet(({textSpan}) => textSpan.start + 100003 * textSpan.length, documentSpansEqual);
304    }
305
306    function getRenameLocationsWorker(
307        projects: Projects,
308        defaultProject: Project,
309        initialLocation: DocumentPosition,
310        findInStrings: boolean,
311        findInComments: boolean,
312        { providePrefixAndSuffixTextForRename }: UserPreferences
313    ): readonly RenameLocation[] {
314        const perProjectResults = getPerProjectReferences(
315            projects,
316            defaultProject,
317            initialLocation,
318            /*isForRename*/ true,
319            (project, position) => project.getLanguageService().findRenameLocations(position.fileName, position.pos, findInStrings, findInComments, providePrefixAndSuffixTextForRename),
320            (renameLocation, cb) => cb(documentSpanLocation(renameLocation)),
321        );
322
323        // No filtering or dedup'ing is required if there's exactly one project
324        if (isArray(perProjectResults)) {
325            return perProjectResults;
326        }
327
328        const results: RenameLocation[] = [];
329        const seen = createDocumentSpanSet();
330
331        perProjectResults.forEach((projectResults, project) => {
332            for (const result of projectResults) {
333                // If there's a mapped location, it'll appear in the results for another project
334                if (!seen.has(result) && !getMappedLocationForProject(documentSpanLocation(result), project)) {
335                    results.push(result);
336                    seen.add(result);
337                }
338            }
339        });
340
341        return results;
342    }
343
344    function getDefinitionLocation(defaultProject: Project, initialLocation: DocumentPosition, isForRename: boolean): DocumentPosition | undefined {
345        const infos = defaultProject.getLanguageService().getDefinitionAtPosition(initialLocation.fileName, initialLocation.pos, /*searchOtherFilesOnly*/ false, /*stopAtAlias*/ isForRename);
346        const info = infos && firstOrUndefined(infos);
347        // Note that the value of `isLocal` may depend on whether or not the checker has run on the containing file
348        // (implying that FAR cascading behavior may depend on request order)
349        return info && !info.isLocal ? { fileName: info.fileName, pos: info.textSpan.start } : undefined;
350    }
351
352    function getReferencesWorker(
353        projects: Projects,
354        defaultProject: Project,
355        initialLocation: DocumentPosition,
356        logger: Logger,
357    ): readonly ReferencedSymbol[] {
358        const perProjectResults = getPerProjectReferences(
359            projects,
360            defaultProject,
361            initialLocation,
362            /*isForRename*/ false,
363            (project, position) => {
364                logger.info(`Finding references to ${position.fileName} position ${position.pos} in project ${project.getProjectName()}`);
365                return project.getLanguageService().findReferences(position.fileName, position.pos);
366            },
367            (referencedSymbol, cb) => {
368                cb(documentSpanLocation(referencedSymbol.definition));
369                for (const ref of referencedSymbol.references) {
370                    cb(documentSpanLocation(ref));
371                }
372            },
373        );
374
375        // No re-mapping or isDefinition updatses are required if there's exactly one project
376        if (isArray(perProjectResults)) {
377            return perProjectResults;
378        }
379
380        // `isDefinition` is only (definitely) correct in `defaultProject` because we might
381        // have started the other project searches from related symbols.  Propagate the
382        // correct results to all other projects.
383
384        const defaultProjectResults = perProjectResults.get(defaultProject);
385        if (defaultProjectResults?.[0]?.references[0]?.isDefinition === undefined) {
386            // Clear all isDefinition properties
387            perProjectResults.forEach(projectResults => {
388                for (const referencedSymbol of projectResults) {
389                    for (const ref of referencedSymbol.references) {
390                        delete ref.isDefinition;
391                    }
392                }
393            });
394        }
395        else {
396            // Correct isDefinition properties from projects other than defaultProject
397            const knownSymbolSpans = createDocumentSpanSet();
398            for (const referencedSymbol of defaultProjectResults) {
399                for (const ref of referencedSymbol.references) {
400                    if (ref.isDefinition) {
401                        knownSymbolSpans.add(ref);
402                        // One is enough - updateIsDefinitionOfReferencedSymbols will fill out the set based on symbols
403                        break;
404                    }
405                }
406            }
407
408            const updatedProjects = new Set<Project>();
409            while (true) {
410                let progress = false;
411                perProjectResults.forEach((referencedSymbols, project) => {
412                    if (updatedProjects.has(project)) return;
413                    const updated = project.getLanguageService().updateIsDefinitionOfReferencedSymbols(referencedSymbols, knownSymbolSpans);
414                    if (updated) {
415                        updatedProjects.add(project);
416                        progress = true;
417                    }
418                });
419                if (!progress) break;
420            }
421
422            perProjectResults.forEach((referencedSymbols, project) => {
423                if (updatedProjects.has(project)) return;
424                for (const referencedSymbol of referencedSymbols) {
425                    for (const ref of referencedSymbol.references) {
426                        ref.isDefinition = false;
427                    }
428                }
429            });
430        }
431
432        // We need to de-duplicate and aggregate the results by choosing an authoritative version
433        // of each definition and merging references from all the projects where they appear.
434
435        const results: ReferencedSymbol[] = [];
436        const seenRefs = createDocumentSpanSet(); // It doesn't make sense to have a reference in two definition lists, so we de-dup globally
437
438        // TODO: We might end up with a more logical allocation of refs to defs if we pre-sorted the defs by descending ref-count.
439        // Otherwise, it just ends up attached to the first corresponding def we happen to process.  The others may or may not be
440        // dropped later when we check for defs with ref-count 0.
441        perProjectResults.forEach((projectResults, project) => {
442            for (const referencedSymbol of projectResults) {
443                const mappedDefinitionFile = getMappedLocationForProject(documentSpanLocation(referencedSymbol.definition), project);
444                const definition: ReferencedSymbolDefinitionInfo = mappedDefinitionFile === undefined ?
445                    referencedSymbol.definition :
446                    {
447                        ...referencedSymbol.definition,
448                        textSpan: createTextSpan(mappedDefinitionFile.pos, referencedSymbol.definition.textSpan.length), // Why would the length be the same in the original?
449                        fileName: mappedDefinitionFile.fileName,
450                        contextSpan: getMappedContextSpanForProject(referencedSymbol.definition, project)
451                    };
452
453                let symbolToAddTo = find(results, o => documentSpansEqual(o.definition, definition));
454                if (!symbolToAddTo) {
455                    symbolToAddTo = { definition, references: [] };
456                    results.push(symbolToAddTo);
457                }
458
459                for (const ref of referencedSymbol.references) {
460                    if (!seenRefs.has(ref) && !getMappedLocationForProject(documentSpanLocation(ref), project)) {
461                        seenRefs.add(ref);
462                        symbolToAddTo.references.push(ref);
463                    }
464                }
465            }
466        });
467
468        return results.filter(o => o.references.length !== 0);
469    }
470
471    interface ProjectAndLocation {
472        readonly project: Project;
473        readonly location: DocumentPosition;
474    }
475
476    function forEachProjectInProjects(projects: Projects, path: string | undefined, cb: (project: Project, path: string | undefined) => void): void {
477        for (const project of isArray(projects) ? projects : projects.projects) {
478            cb(project, path);
479        }
480        if (!isArray(projects) && projects.symLinkedProjects) {
481            projects.symLinkedProjects.forEach((symlinkedProjects, symlinkedPath) => {
482                for (const project of symlinkedProjects) {
483                    cb(project, symlinkedPath);
484                }
485            });
486        }
487    }
488
489    /**
490     * @param projects Projects initially known to contain {@link initialLocation}
491     * @param defaultProject The default project containing {@link initialLocation}
492     * @param initialLocation Where the search operation was triggered
493     * @param getResultsForPosition This is where you plug in `findReferences`, `renameLocation`, etc
494     * @param forPositionInResult Given an item returned by {@link getResultsForPosition} enumerate the positions referred to by that result
495     * @returns In the common case where there's only one project, returns an array of results from {@link getResultsForPosition}.
496     * If multiple projects were searched - even if they didn't return results - the result will be a map from project to per-project results.
497     */
498    function getPerProjectReferences<TResult>(
499        projects: Projects,
500        defaultProject: Project,
501        initialLocation: DocumentPosition,
502        isForRename: boolean,
503        getResultsForPosition: (project: Project, location: DocumentPosition) => readonly TResult[] | undefined,
504        forPositionInResult: (result: TResult, cb: (location: DocumentPosition) => void) => void,
505    ): readonly TResult[] | ESMap<Project, readonly TResult[]> {
506        // If `getResultsForPosition` returns results for a project, they go in here
507        const resultsMap = new Map<Project, readonly TResult[]>();
508
509        const queue = createQueue<ProjectAndLocation>();
510
511        // In order to get accurate isDefinition values for `defaultProject`,
512        // we need to ensure that it is searched from `initialLocation`.
513        // The easiest way to do this is to search it first.
514        queue.enqueue({ project: defaultProject, location: initialLocation });
515
516        // This will queue `defaultProject` a second time, but it will be dropped
517        // as a dup when it is dequeued.
518        forEachProjectInProjects(projects, initialLocation.fileName, (project, path) => {
519            const location = { fileName: path!, pos: initialLocation.pos };
520            queue.enqueue({ project, location });
521        });
522
523        const projectService = defaultProject.projectService;
524        const cancellationToken = defaultProject.getCancellationToken();
525
526        const defaultDefinition = getDefinitionLocation(defaultProject, initialLocation, isForRename);
527
528        // Don't call these unless !!defaultDefinition
529        const getGeneratedDefinition = memoize(() => defaultProject.isSourceOfProjectReferenceRedirect(defaultDefinition!.fileName) ?
530            defaultDefinition :
531            defaultProject.getLanguageService().getSourceMapper().tryGetGeneratedPosition(defaultDefinition!));
532        const getSourceDefinition = memoize(() => defaultProject.isSourceOfProjectReferenceRedirect(defaultDefinition!.fileName) ?
533            defaultDefinition :
534            defaultProject.getLanguageService().getSourceMapper().tryGetSourcePosition(defaultDefinition!));
535
536        // The keys of resultsMap allow us to check which projects have already been searched, but we also
537        // maintain a set of strings because that's what `loadAncestorProjectTree` wants.
538        const searchedProjectKeys = new Set<string>();
539
540        onCancellation:
541        while (!queue.isEmpty()) {
542            while (!queue.isEmpty()) {
543                if (cancellationToken.isCancellationRequested()) break onCancellation;
544
545                const { project, location } = queue.dequeue();
546
547                if (resultsMap.has(project)) continue;
548                if (isLocationProjectReferenceRedirect(project, location)) continue;
549
550                const projectResults = searchPosition(project, location);
551                resultsMap.set(project, projectResults ?? emptyArray);
552                searchedProjectKeys.add(getProjectKey(project));
553            }
554
555            // At this point, we know about all projects passed in as arguments and any projects in which
556            // `getResultsForPosition` has returned results.  We expand that set to include any projects
557            // downstream from any of these and then queue new initial-position searches for any new project
558            // containing `initialLocation`.
559            if (defaultDefinition) {
560                // This seems to mean "load all projects downstream from any member of `seenProjects`".
561                projectService.loadAncestorProjectTree(searchedProjectKeys);
562                projectService.forEachEnabledProject(project => {
563                    if (cancellationToken.isCancellationRequested()) return; // There's no mechanism for skipping the remaining projects
564                    if (resultsMap.has(project)) return; // Can loop forever without this (enqueue here, dequeue above, repeat)
565                    const location = mapDefinitionInProject(defaultDefinition, project, getGeneratedDefinition, getSourceDefinition);
566                    if (location) {
567                        queue.enqueue({ project, location });
568                    }
569                });
570            }
571        }
572
573        // In the common case where there's only one project, return a simpler result to make
574        // it easier for the caller to skip post-processing.
575        if (resultsMap.size === 1) {
576            const it = resultsMap.values().next();
577            Debug.assert(!it.done);
578            return it.value;
579        }
580
581        return resultsMap;
582
583        function searchPosition(project: Project, location: DocumentPosition): readonly TResult[] | undefined {
584            const projectResults = getResultsForPosition(project, location);
585            if (!projectResults) return undefined;
586
587            for (const result of projectResults) {
588                forPositionInResult(result, position => {
589                    // This may trigger a search for a tsconfig, but there are several layers of caching that make it inexpensive
590                    const originalLocation = projectService.getOriginalLocationEnsuringConfiguredProject(project, position);
591                    if (!originalLocation) return;
592
593                    const originalScriptInfo = projectService.getScriptInfo(originalLocation.fileName)!;
594
595                    for (const project of originalScriptInfo.containingProjects) {
596                        if (!project.isOrphan() && !resultsMap.has(project)) { // Optimization: don't enqueue if will be discarded
597                            queue.enqueue({ project, location: originalLocation });
598                        }
599                    }
600
601                    const symlinkedProjectsMap = projectService.getSymlinkedProjects(originalScriptInfo);
602                    if (symlinkedProjectsMap) {
603                        symlinkedProjectsMap.forEach((symlinkedProjects, symlinkedPath) => {
604                            for (const symlinkedProject of symlinkedProjects) {
605                                if (!symlinkedProject.isOrphan() && !resultsMap.has(symlinkedProject)) { // Optimization: don't enqueue if will be discarded
606                                    queue.enqueue({ project: symlinkedProject, location: { fileName: symlinkedPath as string, pos: originalLocation.pos } });
607                                }
608                            }
609                        });
610                    }
611                });
612            }
613
614            return projectResults;
615        }
616    }
617
618    function mapDefinitionInProject(
619        definition: DocumentPosition,
620        project: Project,
621        getGeneratedDefinition: () => DocumentPosition | undefined,
622        getSourceDefinition: () => DocumentPosition | undefined
623    ): DocumentPosition | undefined {
624        // If the definition is actually from the project, definition is correct as is
625        if (project.containsFile(toNormalizedPath(definition.fileName)) &&
626            !isLocationProjectReferenceRedirect(project, definition)) {
627            return definition;
628        }
629        const generatedDefinition = getGeneratedDefinition();
630        if (generatedDefinition && project.containsFile(toNormalizedPath(generatedDefinition.fileName))) return generatedDefinition;
631        const sourceDefinition = getSourceDefinition();
632        return sourceDefinition && project.containsFile(toNormalizedPath(sourceDefinition.fileName)) ? sourceDefinition : undefined;
633    }
634
635    function isLocationProjectReferenceRedirect(project: Project, location: DocumentPosition | undefined) {
636        if (!location) return false;
637        const program = project.getLanguageService().getProgram();
638        if (!program) return false;
639        const sourceFile = program.getSourceFile(location.fileName);
640
641        // It is possible that location is attached to project but
642        // the program actually includes its redirect instead.
643        // This happens when rootFile in project is one of the file from referenced project
644        // Thus root is attached but program doesnt have the actual .ts file but .d.ts
645        // If this is not the file we were actually looking, return rest of the toDo
646        return !!sourceFile &&
647            sourceFile.resolvedPath !== sourceFile.path &&
648            sourceFile.resolvedPath !== project.toPath(location.fileName);
649    }
650
651    function getProjectKey(project: Project) {
652        return isConfiguredProject(project) ? project.canonicalConfigFilePath : project.getProjectName();
653    }
654
655    function documentSpanLocation({ fileName, textSpan }: DocumentSpan): DocumentPosition {
656        return { fileName, pos: textSpan.start };
657    }
658
659    function getMappedLocationForProject(location: DocumentPosition, project: Project): DocumentPosition | undefined {
660        return getMappedLocation(location, project.getSourceMapper(), p => project.projectService.fileExists(p as NormalizedPath));
661    }
662
663    function getMappedDocumentSpanForProject(documentSpan: DocumentSpan, project: Project): DocumentSpan | undefined {
664        return getMappedDocumentSpan(documentSpan, project.getSourceMapper(), p => project.projectService.fileExists(p as NormalizedPath));
665    }
666
667    function getMappedContextSpanForProject(documentSpan: DocumentSpan, project: Project): TextSpan | undefined {
668        return getMappedContextSpan(documentSpan, project.getSourceMapper(), p => project.projectService.fileExists(p as NormalizedPath));
669    }
670
671    const invalidPartialSemanticModeCommands: readonly CommandNames[] = [
672        CommandNames.OpenExternalProject,
673        CommandNames.OpenExternalProjects,
674        CommandNames.CloseExternalProject,
675        CommandNames.SynchronizeProjectList,
676        CommandNames.EmitOutput,
677        CommandNames.CompileOnSaveAffectedFileList,
678        CommandNames.CompileOnSaveEmitFile,
679        CommandNames.CompilerOptionsDiagnosticsFull,
680        CommandNames.EncodedSemanticClassificationsFull,
681        CommandNames.SemanticDiagnosticsSync,
682        CommandNames.SuggestionDiagnosticsSync,
683        CommandNames.GeterrForProject,
684        CommandNames.Reload,
685        CommandNames.ReloadProjects,
686        CommandNames.GetCodeFixes,
687        CommandNames.GetCodeFixesFull,
688        CommandNames.GetCombinedCodeFix,
689        CommandNames.GetCombinedCodeFixFull,
690        CommandNames.ApplyCodeActionCommand,
691        CommandNames.GetSupportedCodeFixes,
692        CommandNames.GetApplicableRefactors,
693        CommandNames.GetEditsForRefactor,
694        CommandNames.GetEditsForRefactorFull,
695        CommandNames.OrganizeImports,
696        CommandNames.OrganizeImportsFull,
697        CommandNames.GetEditsForFileRename,
698        CommandNames.GetEditsForFileRenameFull,
699        CommandNames.PrepareCallHierarchy,
700        CommandNames.ProvideCallHierarchyIncomingCalls,
701        CommandNames.ProvideCallHierarchyOutgoingCalls,
702    ];
703
704    const invalidSyntacticModeCommands: readonly CommandNames[] = [
705        ...invalidPartialSemanticModeCommands,
706        CommandNames.Definition,
707        CommandNames.DefinitionFull,
708        CommandNames.DefinitionAndBoundSpan,
709        CommandNames.DefinitionAndBoundSpanFull,
710        CommandNames.TypeDefinition,
711        CommandNames.Implementation,
712        CommandNames.ImplementationFull,
713        CommandNames.References,
714        CommandNames.ReferencesFull,
715        CommandNames.Rename,
716        CommandNames.RenameLocationsFull,
717        CommandNames.RenameInfoFull,
718        CommandNames.Quickinfo,
719        CommandNames.QuickinfoFull,
720        CommandNames.CompletionInfo,
721        CommandNames.Completions,
722        CommandNames.CompletionsFull,
723        CommandNames.CompletionDetails,
724        CommandNames.CompletionDetailsFull,
725        CommandNames.SignatureHelp,
726        CommandNames.SignatureHelpFull,
727        CommandNames.Navto,
728        CommandNames.NavtoFull,
729        CommandNames.Occurrences,
730        CommandNames.DocumentHighlights,
731        CommandNames.DocumentHighlightsFull,
732    ];
733
734    export interface SessionOptions {
735        host: ServerHost;
736        cancellationToken: ServerCancellationToken;
737        useSingleInferredProject: boolean;
738        useInferredProjectPerProjectRoot: boolean;
739        typingsInstaller: ITypingsInstaller;
740        byteLength: (buf: string, encoding?: string) => number;
741        hrtime: (start?: number[]) => number[];
742        logger: Logger;
743        /**
744         * If falsy, all events are suppressed.
745         */
746        canUseEvents: boolean;
747        eventHandler?: ProjectServiceEventHandler;
748        /** Has no effect if eventHandler is also specified. */
749        suppressDiagnosticEvents?: boolean;
750        /** @deprecated use serverMode instead */
751        syntaxOnly?: boolean;
752        serverMode?: LanguageServiceMode;
753        throttleWaitMilliseconds?: number;
754        noGetErrOnBackgroundUpdate?: boolean;
755
756        globalPlugins?: readonly string[];
757        pluginProbeLocations?: readonly string[];
758        allowLocalPluginLoads?: boolean;
759        typesMapLocation?: string;
760    }
761
762    export class Session<TMessage = string> implements EventSender {
763        private readonly gcTimer: GcTimer;
764        protected projectService: ProjectService;
765        private changeSeq = 0;
766
767        private performanceData: protocol.PerformanceData | undefined;
768
769        private currentRequestId!: number;
770        private errorCheck: MultistepOperation;
771
772        protected host: ServerHost;
773        private readonly cancellationToken: ServerCancellationToken;
774        protected readonly typingsInstaller: ITypingsInstaller;
775        protected byteLength: (buf: string, encoding?: string) => number;
776        private hrtime: (start?: number[]) => number[];
777        protected logger: Logger;
778
779        protected canUseEvents: boolean;
780        private suppressDiagnosticEvents?: boolean;
781        private eventHandler: ProjectServiceEventHandler | undefined;
782        private readonly noGetErrOnBackgroundUpdate?: boolean;
783
784        constructor(opts: SessionOptions) {
785            this.host = opts.host;
786            this.cancellationToken = opts.cancellationToken;
787            this.typingsInstaller = opts.typingsInstaller;
788            this.byteLength = opts.byteLength;
789            this.hrtime = opts.hrtime;
790            this.logger = opts.logger;
791            this.canUseEvents = opts.canUseEvents;
792            this.suppressDiagnosticEvents = opts.suppressDiagnosticEvents;
793            this.noGetErrOnBackgroundUpdate = opts.noGetErrOnBackgroundUpdate;
794
795            const { throttleWaitMilliseconds } = opts;
796
797            this.eventHandler = this.canUseEvents
798                ? opts.eventHandler || (event => this.defaultEventHandler(event))
799                : undefined;
800            const multistepOperationHost: MultistepOperationHost = {
801                executeWithRequestId: (requestId, action) => this.executeWithRequestId(requestId, action),
802                getCurrentRequestId: () => this.currentRequestId,
803                getServerHost: () => this.host,
804                logError: (err, cmd) => this.logError(err, cmd),
805                sendRequestCompletedEvent: requestId => this.sendRequestCompletedEvent(requestId),
806                isCancellationRequested: () => this.cancellationToken.isCancellationRequested()
807            };
808            this.errorCheck = new MultistepOperation(multistepOperationHost);
809            const settings: ProjectServiceOptions = {
810                host: this.host,
811                logger: this.logger,
812                cancellationToken: this.cancellationToken,
813                useSingleInferredProject: opts.useSingleInferredProject,
814                useInferredProjectPerProjectRoot: opts.useInferredProjectPerProjectRoot,
815                typingsInstaller: this.typingsInstaller,
816                throttleWaitMilliseconds,
817                eventHandler: this.eventHandler,
818                suppressDiagnosticEvents: this.suppressDiagnosticEvents,
819                globalPlugins: opts.globalPlugins,
820                pluginProbeLocations: opts.pluginProbeLocations,
821                allowLocalPluginLoads: opts.allowLocalPluginLoads,
822                typesMapLocation: opts.typesMapLocation,
823                syntaxOnly: opts.syntaxOnly,
824                serverMode: opts.serverMode,
825                session: this
826            };
827            this.projectService = new ProjectService(settings);
828            this.projectService.setPerformanceEventHandler(this.performanceEventHandler.bind(this));
829            this.gcTimer = new GcTimer(this.host, /*delay*/ 7000, this.logger);
830
831            // Make sure to setup handlers to throw error for not allowed commands on syntax server
832            switch (this.projectService.serverMode) {
833                case LanguageServiceMode.Semantic:
834                    break;
835                case LanguageServiceMode.PartialSemantic:
836                    invalidPartialSemanticModeCommands.forEach(commandName =>
837                        this.handlers.set(commandName, request => {
838                            throw new Error(`Request: ${request.command} not allowed in LanguageServiceMode.PartialSemantic`);
839                        })
840                    );
841                    break;
842                case LanguageServiceMode.Syntactic:
843                    invalidSyntacticModeCommands.forEach(commandName =>
844                        this.handlers.set(commandName, request => {
845                            throw new Error(`Request: ${request.command} not allowed in LanguageServiceMode.Syntactic`);
846                        })
847                    );
848                    break;
849                default:
850                    Debug.assertNever(this.projectService.serverMode);
851            }
852        }
853
854        private sendRequestCompletedEvent(requestId: number): void {
855            this.event<protocol.RequestCompletedEventBody>({ request_seq: requestId }, "requestCompleted");
856        }
857
858        private addPerformanceData(key: keyof protocol.PerformanceData, value: number) {
859            if (!this.performanceData) {
860                this.performanceData = {};
861            }
862            this.performanceData[key] = (this.performanceData[key] ?? 0) + value;
863        }
864
865        private performanceEventHandler(event: PerformanceEvent) {
866            switch (event.kind) {
867                case "UpdateGraph":
868                    this.addPerformanceData("updateGraphDurationMs", event.durationMs);
869                    break;
870                case "CreatePackageJsonAutoImportProvider":
871                    this.addPerformanceData("createAutoImportProviderProgramDurationMs", event.durationMs);
872                    break;
873            }
874        }
875
876        private defaultEventHandler(event: ProjectServiceEvent) {
877            switch (event.eventName) {
878                case ProjectsUpdatedInBackgroundEvent:
879                    const { openFiles } = event.data;
880                    this.projectsUpdatedInBackgroundEvent(openFiles);
881                    break;
882                case ProjectLoadingStartEvent:
883                    const { project, reason } = event.data;
884                    this.event<protocol.ProjectLoadingStartEventBody>(
885                        { projectName: project.getProjectName(), reason },
886                        ProjectLoadingStartEvent);
887                    break;
888                case ProjectLoadingFinishEvent:
889                    const { project: finishProject } = event.data;
890                    this.event<protocol.ProjectLoadingFinishEventBody>({ projectName: finishProject.getProjectName() }, ProjectLoadingFinishEvent);
891                    break;
892                case LargeFileReferencedEvent:
893                    const { file, fileSize, maxFileSize } = event.data;
894                    this.event<protocol.LargeFileReferencedEventBody>({ file, fileSize, maxFileSize }, LargeFileReferencedEvent);
895                    break;
896                case ConfigFileDiagEvent:
897                    const { triggerFile, configFileName: configFile, diagnostics } = event.data;
898                    const bakedDiags = map(diagnostics, diagnostic => formatDiagnosticToProtocol(diagnostic, /*includeFileName*/ true));
899                    this.event<protocol.ConfigFileDiagnosticEventBody>({
900                        triggerFile,
901                        configFile,
902                        diagnostics: bakedDiags
903                    }, ConfigFileDiagEvent);
904                    break;
905                case ProjectLanguageServiceStateEvent: {
906                    const eventName: protocol.ProjectLanguageServiceStateEventName = ProjectLanguageServiceStateEvent;
907                    this.event<protocol.ProjectLanguageServiceStateEventBody>({
908                        projectName: event.data.project.getProjectName(),
909                        languageServiceEnabled: event.data.languageServiceEnabled
910                    }, eventName);
911                    break;
912                }
913                case ProjectInfoTelemetryEvent: {
914                    const eventName: protocol.TelemetryEventName = "telemetry";
915                    this.event<protocol.TelemetryEventBody>({
916                        telemetryEventName: event.eventName,
917                        payload: event.data,
918                    }, eventName);
919                    break;
920                }
921            }
922        }
923
924        private projectsUpdatedInBackgroundEvent(openFiles: string[]): void {
925            this.projectService.logger.info(`got projects updated in background, updating diagnostics for ${openFiles}`);
926            if (openFiles.length) {
927                if (!this.suppressDiagnosticEvents && !this.noGetErrOnBackgroundUpdate) {
928                    // For now only queue error checking for open files. We can change this to include non open files as well
929                    this.errorCheck.startNew(next => this.updateErrorCheck(next, openFiles, 100, /*requireOpen*/ true));
930                }
931
932                // Send project changed event
933                this.event<protocol.ProjectsUpdatedInBackgroundEventBody>({
934                    openFiles
935                }, ProjectsUpdatedInBackgroundEvent);
936            }
937        }
938
939        public logError(err: Error, cmd: string): void {
940            this.logErrorWorker(err, cmd);
941        }
942
943        private logErrorWorker(err: Error & PossibleProgramFileInfo, cmd: string, fileRequest?: protocol.FileRequestArgs): void {
944            let msg = "Exception on executing command " + cmd;
945            if (err.message) {
946                msg += ":\n" + indent(err.message);
947                if ((err as StackTraceError).stack) {
948                    msg += "\n" + indent((err as StackTraceError).stack!);
949                }
950            }
951
952            if (this.logger.hasLevel(LogLevel.verbose)) {
953                if (fileRequest) {
954                    try {
955                        const { file, project } = this.getFileAndProject(fileRequest);
956                        const scriptInfo = project.getScriptInfoForNormalizedPath(file);
957                        if (scriptInfo) {
958                            const text = getSnapshotText(scriptInfo.getSnapshot());
959                            msg += `\n\nFile text of ${fileRequest.file}:${indent(text)}\n`;
960                        }
961                    }
962                    catch { } // eslint-disable-line no-empty
963                }
964
965
966                if (err.ProgramFiles) {
967                    msg += `\n\nProgram files: ${JSON.stringify(err.ProgramFiles)}\n`;
968                    msg += `\n\nProjects::\n`;
969                    let counter = 0;
970                    const addProjectInfo = (project: Project) => {
971                        msg += `\nProject '${project.projectName}' (${ProjectKind[project.projectKind]}) ${counter}\n`;
972                        msg += project.filesToString(/*writeProjectFileNames*/ true);
973                        msg += "\n-----------------------------------------------\n";
974                        counter++;
975                    };
976                    this.projectService.externalProjects.forEach(addProjectInfo);
977                    this.projectService.configuredProjects.forEach(addProjectInfo);
978                    this.projectService.inferredProjects.forEach(addProjectInfo);
979                }
980            }
981
982            this.logger.msg(msg, Msg.Err);
983        }
984
985        public send(msg: protocol.Message) {
986            if (msg.type === "event" && !this.canUseEvents) {
987                if (this.logger.hasLevel(LogLevel.verbose)) {
988                    this.logger.info(`Session does not support events: ignored event: ${JSON.stringify(msg)}`);
989                }
990                return;
991            }
992            this.writeMessage(msg);
993        }
994
995        protected writeMessage(msg: protocol.Message) {
996            const msgText = formatMessage(msg, this.logger, this.byteLength, this.host.newLine);
997            perfLogger.logEvent(`Response message size: ${msgText.length}`);
998            this.host.write(msgText);
999        }
1000
1001        public event<T extends object>(body: T, eventName: string): void {
1002            this.send(toEvent(eventName, body));
1003        }
1004
1005        // For backwards-compatibility only.
1006        /** @deprecated */
1007        public output(info: any, cmdName: string, reqSeq?: number, errorMsg?: string): void {
1008            this.doOutput(info, cmdName, reqSeq!, /*success*/ !errorMsg, errorMsg); // TODO: GH#18217
1009        }
1010
1011        private doOutput(info: {} | undefined, cmdName: string, reqSeq: number, success: boolean, message?: string): void {
1012            const res: protocol.Response = {
1013                seq: 0,
1014                type: "response",
1015                command: cmdName,
1016                request_seq: reqSeq,
1017                success,
1018                performanceData: this.performanceData
1019            };
1020
1021            if (success) {
1022                let metadata: unknown;
1023                if (isArray(info)) {
1024                    res.body = info;
1025                    metadata = (info as WithMetadata<readonly any[]>).metadata;
1026                    delete (info as WithMetadata<readonly any[]>).metadata;
1027                }
1028                else if (typeof info === "object") {
1029                    if ((info as WithMetadata<{}>).metadata) {
1030                        const { metadata: infoMetadata, ...body } = (info as WithMetadata<{}>);
1031                        res.body = body;
1032                        metadata = infoMetadata;
1033                    }
1034                    else {
1035                        res.body = info;
1036                    }
1037                }
1038                else {
1039                    res.body = info;
1040                }
1041                if (metadata) res.metadata = metadata;
1042            }
1043            else {
1044                Debug.assert(info === undefined);
1045            }
1046            if (message) {
1047                res.message = message;
1048            }
1049            this.send(res);
1050        }
1051
1052        private semanticCheck(file: NormalizedPath, project: Project) {
1053            tracing?.push(tracing.Phase.Session, "semanticCheck", { file, configFilePath: (project as ConfiguredProject).canonicalConfigFilePath }); // undefined is fine if the cast fails
1054            const diags = isDeclarationFileInJSOnlyNonConfiguredProject(project, file)
1055                ? emptyArray
1056                : project.getLanguageService().getSemanticDiagnostics(file).filter(d => !!d.file);
1057            this.sendDiagnosticsEvent(file, project, diags, "semanticDiag");
1058            tracing?.pop();
1059        }
1060
1061        private syntacticCheck(file: NormalizedPath, project: Project) {
1062            tracing?.push(tracing.Phase.Session, "syntacticCheck", { file, configFilePath: (project as ConfiguredProject).canonicalConfigFilePath }); // undefined is fine if the cast fails
1063            this.sendDiagnosticsEvent(file, project, project.getLanguageService().getSyntacticDiagnostics(file), "syntaxDiag");
1064            tracing?.pop();
1065        }
1066
1067        private suggestionCheck(file: NormalizedPath, project: Project) {
1068            tracing?.push(tracing.Phase.Session, "suggestionCheck", { file, configFilePath: (project as ConfiguredProject).canonicalConfigFilePath }); // undefined is fine if the cast fails
1069            this.sendDiagnosticsEvent(file, project, project.getLanguageService().getSuggestionDiagnostics(file), "suggestionDiag");
1070            tracing?.pop();
1071        }
1072
1073        private sendDiagnosticsEvent(file: NormalizedPath, project: Project, diagnostics: readonly Diagnostic[], kind: protocol.DiagnosticEventKind): void {
1074            try {
1075                this.event<protocol.DiagnosticEventBody>({ file, diagnostics: diagnostics.map(diag => formatDiag(file, project, diag)) }, kind);
1076            }
1077            catch (err) {
1078                this.logError(err, kind);
1079            }
1080        }
1081
1082        /** It is the caller's responsibility to verify that `!this.suppressDiagnosticEvents`. */
1083        private updateErrorCheck(next: NextStep, checkList: readonly string[] | readonly PendingErrorCheck[], ms: number, requireOpen = true) {
1084            Debug.assert(!this.suppressDiagnosticEvents); // Caller's responsibility
1085
1086            const seq = this.changeSeq;
1087            const followMs = Math.min(ms, 200);
1088
1089            let index = 0;
1090            const goNext = () => {
1091                index++;
1092                if (checkList.length > index) {
1093                    next.delay(followMs, checkOne);
1094                }
1095            };
1096            const checkOne = () => {
1097                if (this.changeSeq !== seq) {
1098                    return;
1099                }
1100
1101                let item: string | PendingErrorCheck | undefined = checkList[index];
1102                if (isString(item)) {
1103                    // Find out project for the file name
1104                    item = this.toPendingErrorCheck(item);
1105                    if (!item) {
1106                        // Ignore file if there is no project for the file
1107                        goNext();
1108                        return;
1109                    }
1110                }
1111
1112                const { fileName, project } = item;
1113
1114                // Ensure the project is up to date before checking if this file is present in the project.
1115                updateProjectIfDirty(project);
1116                if (!project.containsFile(fileName, requireOpen)) {
1117                    return;
1118                }
1119
1120                this.syntacticCheck(fileName, project);
1121                if (this.changeSeq !== seq) {
1122                    return;
1123                }
1124
1125                // Don't provide semantic diagnostics unless we're in full semantic mode.
1126                if (project.projectService.serverMode !== LanguageServiceMode.Semantic) {
1127                    goNext();
1128                    return;
1129                }
1130                next.immediate(() => {
1131                    this.semanticCheck(fileName, project);
1132                    if (this.changeSeq !== seq) {
1133                        return;
1134                    }
1135
1136                    if (this.getPreferences(fileName).disableSuggestions) {
1137                        goNext();
1138                        return;
1139                    }
1140                    next.immediate(() => {
1141                        this.suggestionCheck(fileName, project);
1142                        goNext();
1143                    });
1144                });
1145            };
1146
1147            if (checkList.length > index && this.changeSeq === seq) {
1148                next.delay(ms, checkOne);
1149            }
1150        }
1151
1152        private cleanProjects(caption: string, projects: Project[]) {
1153            if (!projects) {
1154                return;
1155            }
1156            this.logger.info(`cleaning ${caption}`);
1157            for (const p of projects) {
1158                p.getLanguageService(/*ensureSynchronized*/ false).cleanupSemanticCache();
1159            }
1160        }
1161
1162        private cleanup() {
1163            this.cleanProjects("inferred projects", this.projectService.inferredProjects);
1164            this.cleanProjects("configured projects", arrayFrom(this.projectService.configuredProjects.values()));
1165            this.cleanProjects("external projects", this.projectService.externalProjects);
1166            if (this.host.gc) {
1167                this.logger.info(`host.gc()`);
1168                this.host.gc();
1169            }
1170        }
1171
1172        private getEncodedSyntacticClassifications(args: protocol.EncodedSyntacticClassificationsRequestArgs) {
1173            const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
1174            return languageService.getEncodedSyntacticClassifications(file, args);
1175        }
1176
1177        private getEncodedSemanticClassifications(args: protocol.EncodedSemanticClassificationsRequestArgs) {
1178            const { file, project } = this.getFileAndProject(args);
1179            const format = args.format === "2020" ? SemanticClassificationFormat.TwentyTwenty : SemanticClassificationFormat.Original;
1180            return project.getLanguageService().getEncodedSemanticClassifications(file, args, format);
1181        }
1182
1183        private getProject(projectFileName: string | undefined): Project | undefined {
1184            return projectFileName === undefined ? undefined : this.projectService.findProject(projectFileName);
1185        }
1186
1187        private getConfigFileAndProject(args: protocol.FileRequestArgs): { configFile: NormalizedPath | undefined, project: Project | undefined } {
1188            const project = this.getProject(args.projectFileName);
1189            const file = toNormalizedPath(args.file);
1190
1191            return {
1192                configFile: project && project.hasConfigFile(file) ? file : undefined,
1193                project
1194            };
1195        }
1196
1197        private getConfigFileDiagnostics(configFile: NormalizedPath, project: Project, includeLinePosition: boolean) {
1198            const projectErrors = project.getAllProjectErrors();
1199            const optionsErrors = project.getLanguageService().getCompilerOptionsDiagnostics();
1200            const diagnosticsForConfigFile = filter(
1201                concatenate(projectErrors, optionsErrors),
1202                diagnostic => !!diagnostic.file && diagnostic.file.fileName === configFile
1203            );
1204            return includeLinePosition ?
1205                this.convertToDiagnosticsWithLinePositionFromDiagnosticFile(diagnosticsForConfigFile) :
1206                map(
1207                    diagnosticsForConfigFile,
1208                    diagnostic => formatDiagnosticToProtocol(diagnostic, /*includeFileName*/ false)
1209                );
1210        }
1211
1212        private convertToDiagnosticsWithLinePositionFromDiagnosticFile(diagnostics: readonly Diagnostic[]): protocol.DiagnosticWithLinePosition[] {
1213            return diagnostics.map<protocol.DiagnosticWithLinePosition>(d => ({
1214                message: flattenDiagnosticMessageText(d.messageText, this.host.newLine),
1215                start: d.start!, // TODO: GH#18217
1216                length: d.length!, // TODO: GH#18217
1217                category: diagnosticCategoryName(d),
1218                code: d.code,
1219                source: d.source,
1220                startLocation: (d.file && convertToLocation(getLineAndCharacterOfPosition(d.file, d.start!)))!, // TODO: GH#18217
1221                endLocation: (d.file && convertToLocation(getLineAndCharacterOfPosition(d.file, d.start! + d.length!)))!, // TODO: GH#18217
1222                reportsUnnecessary: d.reportsUnnecessary,
1223                reportsDeprecated: d.reportsDeprecated,
1224                relatedInformation: map(d.relatedInformation, formatRelatedInformation)
1225            }));
1226        }
1227
1228        private getCompilerOptionsDiagnostics(args: protocol.CompilerOptionsDiagnosticsRequestArgs) {
1229            const project = this.getProject(args.projectFileName)!;
1230            // Get diagnostics that dont have associated file with them
1231            // The diagnostics which have file would be in config file and
1232            // would be reported as part of configFileDiagnostics
1233            return this.convertToDiagnosticsWithLinePosition(
1234                filter(
1235                    project.getLanguageService().getCompilerOptionsDiagnostics(),
1236                    diagnostic => !diagnostic.file
1237                ),
1238                /*scriptInfo*/ undefined
1239            );
1240        }
1241
1242        private convertToDiagnosticsWithLinePosition(diagnostics: readonly Diagnostic[], scriptInfo: ScriptInfo | undefined): protocol.DiagnosticWithLinePosition[] {
1243            return diagnostics.map(d => ({
1244                message: flattenDiagnosticMessageText(d.messageText, this.host.newLine),
1245                start: d.start,
1246                length: d.length,
1247                category: diagnosticCategoryName(d),
1248                code: d.code,
1249                source: d.source,
1250                startLocation: scriptInfo && scriptInfo.positionToLineOffset(d.start!), // TODO: GH#18217
1251                endLocation: scriptInfo && scriptInfo.positionToLineOffset(d.start! + d.length!),
1252                reportsUnnecessary: d.reportsUnnecessary,
1253                reportsDeprecated: d.reportsDeprecated,
1254                relatedInformation: map(d.relatedInformation, formatRelatedInformation),
1255            }) as protocol.DiagnosticWithLinePosition);
1256        }
1257
1258        private getDiagnosticsWorker(
1259            args: protocol.FileRequestArgs, isSemantic: boolean, selector: (project: Project, file: string) => readonly Diagnostic[], includeLinePosition: boolean
1260        ): readonly protocol.DiagnosticWithLinePosition[] | readonly protocol.Diagnostic[] {
1261            const { project, file } = this.getFileAndProject(args);
1262            if (isSemantic && isDeclarationFileInJSOnlyNonConfiguredProject(project, file)) {
1263                return emptyArray;
1264            }
1265            const scriptInfo = project.getScriptInfoForNormalizedPath(file);
1266            const diagnostics = selector(project, file);
1267            return includeLinePosition
1268                ? this.convertToDiagnosticsWithLinePosition(diagnostics, scriptInfo)
1269                : diagnostics.map(d => formatDiag(file, project, d));
1270        }
1271
1272        private getDefinition(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): readonly protocol.FileSpanWithContext[] | readonly DefinitionInfo[] {
1273            const { file, project } = this.getFileAndProject(args);
1274            const position = this.getPositionInFile(args, file);
1275            const definitions = this.mapDefinitionInfoLocations(project.getLanguageService().getDefinitionAtPosition(file, position) || emptyArray, project);
1276            return simplifiedResult ? this.mapDefinitionInfo(definitions, project) : definitions.map(Session.mapToOriginalLocation);
1277        }
1278
1279        private mapDefinitionInfoLocations(definitions: readonly DefinitionInfo[], project: Project): readonly DefinitionInfo[] {
1280            return definitions.map((info): DefinitionInfo => {
1281                const newDocumentSpan = getMappedDocumentSpanForProject(info, project);
1282                return !newDocumentSpan ? info : {
1283                    ...newDocumentSpan,
1284                    containerKind: info.containerKind,
1285                    containerName: info.containerName,
1286                    kind: info.kind,
1287                    name: info.name,
1288                    failedAliasResolution: info.failedAliasResolution,
1289                    ...info.unverified && { unverified: info.unverified },
1290                };
1291            });
1292        }
1293
1294        private getDefinitionAndBoundSpan(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): protocol.DefinitionInfoAndBoundSpan | DefinitionInfoAndBoundSpan {
1295            const { file, project } = this.getFileAndProject(args);
1296            const position = this.getPositionInFile(args, file);
1297            const scriptInfo = Debug.checkDefined(project.getScriptInfo(file));
1298
1299            const unmappedDefinitionAndBoundSpan = project.getLanguageService().getDefinitionAndBoundSpan(file, position);
1300
1301            if (!unmappedDefinitionAndBoundSpan || !unmappedDefinitionAndBoundSpan.definitions) {
1302                return {
1303                    definitions: emptyArray,
1304                    textSpan: undefined! // TODO: GH#18217
1305                };
1306            }
1307
1308            const definitions = this.mapDefinitionInfoLocations(unmappedDefinitionAndBoundSpan.definitions, project);
1309            const { textSpan } = unmappedDefinitionAndBoundSpan;
1310
1311            if (simplifiedResult) {
1312                return {
1313                    definitions: this.mapDefinitionInfo(definitions, project),
1314                    textSpan: toProtocolTextSpan(textSpan, scriptInfo)
1315                };
1316            }
1317
1318            return {
1319                definitions: definitions.map(Session.mapToOriginalLocation),
1320                textSpan,
1321            };
1322        }
1323
1324        private findSourceDefinition(args: protocol.FileLocationRequestArgs): readonly protocol.DefinitionInfo[] {
1325            const { file, project } = this.getFileAndProject(args);
1326            const position = this.getPositionInFile(args, file);
1327            const unmappedDefinitions = project.getLanguageService().getDefinitionAtPosition(file, position);
1328            let definitions: readonly DefinitionInfo[] = this.mapDefinitionInfoLocations(unmappedDefinitions || emptyArray, project).slice();
1329            const needsJsResolution = this.projectService.serverMode === LanguageServiceMode.Semantic && (
1330                !some(definitions, d => toNormalizedPath(d.fileName) !== file && !d.isAmbient) ||
1331                some(definitions, d => !!d.failedAliasResolution));
1332
1333            if (needsJsResolution) {
1334                const definitionSet = createSet<DefinitionInfo>(d => d.textSpan.start, documentSpansEqual);
1335                definitions?.forEach(d => definitionSet.add(d));
1336                const noDtsProject = project.getNoDtsResolutionProject([file]);
1337                const ls = noDtsProject.getLanguageService();
1338                const jsDefinitions = ls.getDefinitionAtPosition(file, position, /*searchOtherFilesOnly*/ true, /*stopAtAlias*/ false)
1339                    ?.filter(d => toNormalizedPath(d.fileName) !== file);
1340                if (some(jsDefinitions)) {
1341                    for (const jsDefinition of jsDefinitions) {
1342                        if (jsDefinition.unverified) {
1343                            const refined = tryRefineDefinition(jsDefinition, project.getLanguageService().getProgram()!, ls.getProgram()!);
1344                            if (some(refined)) {
1345                                for (const def of refined) {
1346                                    definitionSet.add(def);
1347                                }
1348                                continue;
1349                            }
1350                        }
1351                        definitionSet.add(jsDefinition);
1352                    }
1353                }
1354                else {
1355                    const ambientCandidates = definitions.filter(d => toNormalizedPath(d.fileName) !== file && d.isAmbient);
1356                    for (const candidate of some(ambientCandidates) ? ambientCandidates : getAmbientCandidatesByClimbingAccessChain()) {
1357                        const fileNameToSearch = findImplementationFileFromDtsFileName(candidate.fileName, file, noDtsProject);
1358                        if (!fileNameToSearch || !ensureRoot(noDtsProject, fileNameToSearch)) {
1359                            continue;
1360                        }
1361                        const noDtsProgram = ls.getProgram()!;
1362                        const fileToSearch = Debug.checkDefined(noDtsProgram.getSourceFile(fileNameToSearch));
1363                        for (const match of searchForDeclaration(candidate.name, fileToSearch, noDtsProgram)) {
1364                            definitionSet.add(match);
1365                        }
1366                    }
1367                }
1368                definitions = arrayFrom(definitionSet.values());
1369            }
1370
1371            definitions = definitions.filter(d => !d.isAmbient && !d.failedAliasResolution);
1372            return this.mapDefinitionInfo(definitions, project);
1373
1374            function findImplementationFileFromDtsFileName(fileName: string, resolveFromFile: string, auxiliaryProject: Project) {
1375                const nodeModulesPathParts = getNodeModulePathParts(fileName,  isOhpm(project.getCompilationSettings().packageManagerType) ? ohModulesPathPart : nodeModulesPathPart);
1376                if (nodeModulesPathParts && fileName.lastIndexOf(nodeModulesPathPart) === nodeModulesPathParts.topLevelNodeModulesIndex) {
1377                    // Second check ensures the fileName only contains one `/node_modules/`. If there's more than one I give up.
1378                    const packageDirectory = fileName.substring(0, nodeModulesPathParts.packageRootIndex);
1379                    const packageJsonCache = project.getModuleResolutionCache()?.getPackageJsonInfoCache();
1380                    const compilerOptions = project.getCompilationSettings();
1381                    const packageJson = getPackageScopeForPath(getNormalizedAbsolutePath(packageDirectory + "/package.json", project.getCurrentDirectory()), getTemporaryModuleResolutionState(packageJsonCache, project, compilerOptions));
1382                    if (!packageJson) return undefined;
1383                    // Use fake options instead of actual compiler options to avoid following export map if the project uses node16 or nodenext -
1384                    // Mapping from an export map entry across packages is out of scope for now. Returned entrypoints will only be what can be
1385                    // resolved from the package root under --moduleResolution node
1386                    const entrypoints = getEntrypointsFromPackageJsonInfo(
1387                        packageJson,
1388                        { moduleResolution: ModuleResolutionKind.NodeJs },
1389                        project,
1390                        project.getModuleResolutionCache());
1391                    // This substring is correct only because we checked for a single `/node_modules/` at the top.
1392                    const packageNamePathPart = fileName.substring(
1393                        nodeModulesPathParts.topLevelPackageNameIndex + 1,
1394                        nodeModulesPathParts.packageRootIndex);
1395                    const packageName = getPackageNameFromTypesPackageName(unmangleScopedPackageName(packageNamePathPart));
1396                    const path = project.toPath(fileName);
1397                    if (entrypoints && some(entrypoints, e => project.toPath(e) === path)) {
1398                        // This file was the main entrypoint of a package. Try to resolve that same package name with
1399                        // the auxiliary project that only resolves to implementation files.
1400                        const [implementationResolution] = auxiliaryProject.resolveModuleNames([packageName], resolveFromFile);
1401                        return implementationResolution?.resolvedFileName;
1402                    }
1403                    else {
1404                        // It wasn't the main entrypoint but we are in node_modules. Try a subpath into the package.
1405                        const pathToFileInPackage = fileName.substring(nodeModulesPathParts.packageRootIndex + 1);
1406                        const specifier = `${packageName}/${removeFileExtension(pathToFileInPackage)}`;
1407                        const [implementationResolution] = auxiliaryProject.resolveModuleNames([specifier], resolveFromFile);
1408                        return implementationResolution?.resolvedFileName;
1409                    }
1410                }
1411                // We're not in node_modules, and we only get to this function if non-dts module resolution failed.
1412                // I'm not sure what else I can do here that isn't already covered by that module resolution.
1413                return undefined;
1414            }
1415
1416            // In 'foo.bar./**/baz', if we got not results on 'baz', see if we can get an ambient definition
1417            // for 'bar' or 'foo' (in that order) so we can search for declarations of 'baz' later.
1418            function getAmbientCandidatesByClimbingAccessChain(): readonly { name: string, fileName: string }[] {
1419                const ls = project.getLanguageService();
1420                const program = ls.getProgram()!;
1421                const initialNode = getTouchingPropertyName(program.getSourceFile(file)!, position);
1422                if ((isStringLiteralLike(initialNode) || isIdentifier(initialNode)) && isAccessExpression(initialNode.parent)) {
1423                    return forEachNameInAccessChainWalkingLeft(initialNode, nameInChain => {
1424                        if (nameInChain === initialNode) return undefined;
1425                        const candidates = ls.getDefinitionAtPosition(file, nameInChain.getStart(), /*searchOtherFilesOnly*/ true, /*stopAtAlias*/ false)
1426                            ?.filter(d => toNormalizedPath(d.fileName) !== file && d.isAmbient)
1427                            .map(d => ({
1428                                fileName: d.fileName,
1429                                name: getTextOfIdentifierOrLiteral(initialNode)
1430                            }));
1431                        if (some(candidates)) {
1432                            return candidates;
1433                        }
1434                    }) || emptyArray;
1435                }
1436                return emptyArray;
1437            }
1438
1439            function tryRefineDefinition(definition: DefinitionInfo, program: Program, noDtsProgram: Program) {
1440                const fileToSearch = noDtsProgram.getSourceFile(definition.fileName);
1441                if (!fileToSearch) {
1442                    return undefined;
1443                }
1444                const initialNode = getTouchingPropertyName(program.getSourceFile(file)!, position);
1445                const symbol = program.getTypeChecker().getSymbolAtLocation(initialNode);
1446                const importSpecifier = symbol && getDeclarationOfKind<ImportSpecifier>(symbol, SyntaxKind.ImportSpecifier);
1447                if (!importSpecifier) return undefined;
1448
1449                const nameToSearch = importSpecifier.propertyName?.text || importSpecifier.name.text;
1450                return searchForDeclaration(nameToSearch, fileToSearch, noDtsProgram);
1451            }
1452
1453            function searchForDeclaration(declarationName: string, fileToSearch: SourceFile, noDtsProgram: Program) {
1454                const matches = FindAllReferences.Core.getTopMostDeclarationNamesInFile(declarationName, fileToSearch);
1455                return mapDefined(matches, match => {
1456                    const symbol = noDtsProgram.getTypeChecker().getSymbolAtLocation(match);
1457                    const decl = getDeclarationFromName(match);
1458                    if (symbol && decl) {
1459                        // I think the last argument to this is supposed to be the start node, but it doesn't seem important.
1460                        // Callers internal to GoToDefinition already get confused about this.
1461                        return GoToDefinition.createDefinitionInfo(decl, noDtsProgram.getTypeChecker(), symbol, decl, /*unverified*/ true);
1462                    }
1463                });
1464            }
1465
1466            function ensureRoot(project: Project, fileName: string) {
1467                const info = project.getScriptInfo(fileName);
1468                if (!info) return false;
1469                if (!project.containsScriptInfo(info)) {
1470                    project.addRoot(info);
1471                    project.updateGraph();
1472                }
1473                return true;
1474            }
1475        }
1476
1477        private getEmitOutput(args: protocol.EmitOutputRequestArgs): EmitOutput | protocol.EmitOutput {
1478            const { file, project } = this.getFileAndProject(args);
1479            if (!project.shouldEmitFile(project.getScriptInfo(file))) {
1480                return { emitSkipped: true, outputFiles: [], diagnostics: [] };
1481            }
1482            const result = project.getLanguageService().getEmitOutput(file);
1483            return args.richResponse ?
1484                {
1485                    ...result,
1486                    diagnostics: args.includeLinePosition ?
1487                        this.convertToDiagnosticsWithLinePositionFromDiagnosticFile(result.diagnostics) :
1488                        result.diagnostics.map(d => formatDiagnosticToProtocol(d, /*includeFileName*/ true))
1489                } :
1490                result;
1491        }
1492
1493        private mapJSDocTagInfo(tags: JSDocTagInfo[] | undefined, project: Project, richResponse: boolean): protocol.JSDocTagInfo[] {
1494            return tags ? tags.map(tag => ({
1495                ...tag,
1496                text: richResponse && Array.isArray(tag.text) ? this.mapDisplayParts(tag.text, project): getTextOfJSDocTagInfo(tag)
1497            })) : [];
1498        }
1499
1500        private mapDisplayParts(parts: SymbolDisplayPart[] | undefined, project: Project): protocol.SymbolDisplayPart[] {
1501            if (!parts) {
1502                return [];
1503            }
1504            return parts.map(part => part.kind !== "linkName" ? part : {
1505                ...part,
1506                target: this.toFileSpan((part as JSDocLinkDisplayPart).target.fileName, (part as JSDocLinkDisplayPart).target.textSpan, project),
1507            });
1508        }
1509
1510        private mapSignatureHelpItems(items: SignatureHelpItem[], project: Project, richResponse: boolean): protocol.SignatureHelpItem[] {
1511            return items.map(item => ({
1512                ...item,
1513                documentation: this.mapDisplayParts(item.documentation, project),
1514                parameters: item.parameters.map(p => ({ ...p, documentation: this.mapDisplayParts(p.documentation, project) })),
1515                tags: this.mapJSDocTagInfo(item.tags, project, richResponse),
1516            }));
1517        }
1518
1519        private mapDefinitionInfo(definitions: readonly DefinitionInfo[], project: Project): readonly protocol.DefinitionInfo[] {
1520            return definitions.map(def => ({ ...this.toFileSpanWithContext(def.fileName, def.textSpan, def.contextSpan, project), ...def.unverified && { unverified: def.unverified } }));
1521        }
1522
1523        /*
1524         * When we map a .d.ts location to .ts, Visual Studio gets confused because there's no associated Roslyn Document in
1525         * the same project which corresponds to the file. VS Code has no problem with this, and luckily we have two protocols.
1526         * This retains the existing behavior for the "simplified" (VS Code) protocol but stores the .d.ts location in a
1527         * set of additional fields, and does the reverse for VS (store the .d.ts location where
1528         * it used to be and stores the .ts location in the additional fields).
1529        */
1530        private static mapToOriginalLocation<T extends DocumentSpan>(def: T): T {
1531            if (def.originalFileName) {
1532                Debug.assert(def.originalTextSpan !== undefined, "originalTextSpan should be present if originalFileName is");
1533                return {
1534                    ...def as any,
1535                    fileName: def.originalFileName,
1536                    textSpan: def.originalTextSpan,
1537                    targetFileName: def.fileName,
1538                    targetTextSpan: def.textSpan,
1539                    contextSpan: def.originalContextSpan,
1540                    targetContextSpan: def.contextSpan
1541                };
1542            }
1543            return def;
1544        }
1545
1546        private toFileSpan(fileName: string, textSpan: TextSpan, project: Project): protocol.FileSpan {
1547            const ls = project.getLanguageService();
1548            const start = ls.toLineColumnOffset!(fileName, textSpan.start); // TODO: GH#18217
1549            const end = ls.toLineColumnOffset!(fileName, textSpanEnd(textSpan));
1550
1551            return {
1552                file: fileName,
1553                start: { line: start.line + 1, offset: start.character + 1 },
1554                end: { line: end.line + 1, offset: end.character + 1 }
1555            };
1556        }
1557
1558        private toFileSpanWithContext(fileName: string, textSpan: TextSpan, contextSpan: TextSpan | undefined, project: Project): protocol.FileSpanWithContext {
1559            const fileSpan = this.toFileSpan(fileName, textSpan, project);
1560            const context = contextSpan && this.toFileSpan(fileName, contextSpan, project);
1561            return context ?
1562                { ...fileSpan, contextStart: context.start, contextEnd: context.end } :
1563                fileSpan;
1564        }
1565
1566        private getTypeDefinition(args: protocol.FileLocationRequestArgs): readonly protocol.FileSpanWithContext[] {
1567            const { file, project } = this.getFileAndProject(args);
1568            const position = this.getPositionInFile(args, file);
1569
1570            const definitions = this.mapDefinitionInfoLocations(project.getLanguageService().getTypeDefinitionAtPosition(file, position) || emptyArray, project);
1571            return this.mapDefinitionInfo(definitions, project);
1572        }
1573
1574        private mapImplementationLocations(implementations: readonly ImplementationLocation[], project: Project): readonly ImplementationLocation[] {
1575            return implementations.map((info): ImplementationLocation => {
1576                const newDocumentSpan = getMappedDocumentSpanForProject(info, project);
1577                return !newDocumentSpan ? info : {
1578                    ...newDocumentSpan,
1579                    kind: info.kind,
1580                    displayParts: info.displayParts,
1581                };
1582            });
1583        }
1584
1585        private getImplementation(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): readonly protocol.FileSpanWithContext[] | readonly ImplementationLocation[] {
1586            const { file, project } = this.getFileAndProject(args);
1587            const position = this.getPositionInFile(args, file);
1588            const implementations = this.mapImplementationLocations(project.getLanguageService().getImplementationAtPosition(file, position) || emptyArray, project);
1589            return simplifiedResult ?
1590                implementations.map(({ fileName, textSpan, contextSpan }) => this.toFileSpanWithContext(fileName, textSpan, contextSpan, project)) :
1591                implementations.map(Session.mapToOriginalLocation);
1592        }
1593
1594        private getOccurrences(args: protocol.FileLocationRequestArgs): readonly protocol.OccurrencesResponseItem[] {
1595            const { file, project } = this.getFileAndProject(args);
1596            const position = this.getPositionInFile(args, file);
1597            const occurrences = project.getLanguageService().getOccurrencesAtPosition(file, position);
1598            return occurrences ?
1599                occurrences.map<protocol.OccurrencesResponseItem>(occurrence => {
1600                    const { fileName, isWriteAccess, textSpan, isInString, contextSpan } = occurrence;
1601                    const scriptInfo = project.getScriptInfo(fileName)!;
1602                    return {
1603                        ...toProtocolTextSpanWithContext(textSpan, contextSpan, scriptInfo),
1604                        file: fileName,
1605                        isWriteAccess,
1606                        ...(isInString ? { isInString } : undefined)
1607                    };
1608                }) :
1609                emptyArray;
1610        }
1611
1612        private getSyntacticDiagnosticsSync(args: protocol.SyntacticDiagnosticsSyncRequestArgs) {
1613            const { configFile } = this.getConfigFileAndProject(args);
1614            if (configFile) {
1615                // all the config file errors are reported as part of semantic check so nothing to report here
1616                return emptyArray;
1617            }
1618
1619            return this.getDiagnosticsWorker(args, /*isSemantic*/ false, (project, file) => project.getLanguageService().getSyntacticDiagnostics(file), !!args.includeLinePosition);
1620        }
1621
1622        private getSemanticDiagnosticsSync(args: protocol.SemanticDiagnosticsSyncRequestArgs) {
1623            const { configFile, project } = this.getConfigFileAndProject(args);
1624            if (configFile) {
1625                return this.getConfigFileDiagnostics(configFile, project!, !!args.includeLinePosition); // TODO: GH#18217
1626            }
1627            return this.getDiagnosticsWorker(args, /*isSemantic*/ true, (project, file) => project.getLanguageService().getSemanticDiagnostics(file).filter(d => !!d.file), !!args.includeLinePosition);
1628        }
1629
1630        private getSuggestionDiagnosticsSync(args: protocol.SuggestionDiagnosticsSyncRequestArgs) {
1631            const { configFile } = this.getConfigFileAndProject(args);
1632            if (configFile) {
1633                // Currently there are no info diagnostics for config files.
1634                return emptyArray;
1635            }
1636            // isSemantic because we don't want to info diagnostics in declaration files for JS-only users
1637            return this.getDiagnosticsWorker(args, /*isSemantic*/ true, (project, file) => project.getLanguageService().getSuggestionDiagnostics(file), !!args.includeLinePosition);
1638        }
1639
1640        private getJsxClosingTag(args: protocol.JsxClosingTagRequestArgs): TextInsertion | undefined {
1641            const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
1642            const position = this.getPositionInFile(args, file);
1643            const tag = languageService.getJsxClosingTagAtPosition(file, position);
1644            return tag === undefined ? undefined : { newText: tag.newText, caretOffset: 0 };
1645        }
1646
1647        private getDocumentHighlights(args: protocol.DocumentHighlightsRequestArgs, simplifiedResult: boolean): readonly protocol.DocumentHighlightsItem[] | readonly DocumentHighlights[] {
1648            const { file, project } = this.getFileAndProject(args);
1649            const position = this.getPositionInFile(args, file);
1650            const documentHighlights = project.getLanguageService().getDocumentHighlights(file, position, args.filesToSearch);
1651
1652            if (!documentHighlights) return emptyArray;
1653            if (!simplifiedResult) return documentHighlights;
1654
1655            return documentHighlights.map<protocol.DocumentHighlightsItem>(({ fileName, highlightSpans }) => {
1656                const scriptInfo = project.getScriptInfo(fileName)!;
1657                return {
1658                    file: fileName,
1659                    highlightSpans: highlightSpans.map(({ textSpan, kind, contextSpan }) => ({
1660                        ...toProtocolTextSpanWithContext(textSpan, contextSpan, scriptInfo),
1661                        kind
1662                    }))
1663                };
1664            });
1665        }
1666
1667        private provideInlayHints(args: protocol.InlayHintsRequestArgs): readonly protocol.InlayHintItem[] {
1668            const { file, project } = this.getFileAndProject(args);
1669            const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!;
1670            const hints = project.getLanguageService().provideInlayHints(file, args, this.getPreferences(file));
1671
1672            return hints.map(hint => ({
1673                ...hint,
1674                position: scriptInfo.positionToLineOffset(hint.position),
1675            }));
1676        }
1677
1678        private setCompilerOptionsForInferredProjects(args: protocol.SetCompilerOptionsForInferredProjectsArgs): void {
1679            this.projectService.setCompilerOptionsForInferredProjects(args.options, args.projectRootPath);
1680        }
1681
1682        private getProjectInfo(args: protocol.ProjectInfoRequestArgs): protocol.ProjectInfo {
1683            return this.getProjectInfoWorker(args.file, args.projectFileName, args.needFileNameList, /*excludeConfigFiles*/ false);
1684        }
1685
1686        private getProjectInfoWorker(uncheckedFileName: string, projectFileName: string | undefined, needFileNameList: boolean, excludeConfigFiles: boolean) {
1687            const { project } = this.getFileAndProjectWorker(uncheckedFileName, projectFileName);
1688            updateProjectIfDirty(project);
1689            const projectInfo = {
1690                configFileName: project.getProjectName(),
1691                languageServiceDisabled: !project.languageServiceEnabled,
1692                fileNames: needFileNameList ? project.getFileNames(/*excludeFilesFromExternalLibraries*/ false, excludeConfigFiles) : undefined
1693            };
1694            return projectInfo;
1695        }
1696
1697        private getRenameInfo(args: protocol.FileLocationRequestArgs): RenameInfo {
1698            const { file, project } = this.getFileAndProject(args);
1699            const position = this.getPositionInFile(args, file);
1700            const preferences = this.getPreferences(file);
1701            return project.getLanguageService().getRenameInfo(file, position, preferences);
1702        }
1703
1704        private getProjects(args: protocol.FileRequestArgs, getScriptInfoEnsuringProjectsUptoDate?: boolean, ignoreNoProjectError?: boolean): Projects {
1705            let projects: readonly Project[] | undefined;
1706            let symLinkedProjects: MultiMap<Path, Project> | undefined;
1707            if (args.projectFileName) {
1708                const project = this.getProject(args.projectFileName);
1709                if (project) {
1710                    projects = [project];
1711                }
1712            }
1713            else {
1714                const scriptInfo = getScriptInfoEnsuringProjectsUptoDate ?
1715                    this.projectService.getScriptInfoEnsuringProjectsUptoDate(args.file) :
1716                    this.projectService.getScriptInfo(args.file);
1717                if (!scriptInfo) {
1718                    if (ignoreNoProjectError) return emptyArray;
1719                    this.projectService.logErrorForScriptInfoNotFound(args.file);
1720                    return Errors.ThrowNoProject();
1721                }
1722                else if (!getScriptInfoEnsuringProjectsUptoDate) {
1723                    // Ensure there are containing projects are present
1724                    this.projectService.ensureDefaultProjectForFile(scriptInfo);
1725                }
1726                projects = scriptInfo.containingProjects;
1727                symLinkedProjects = this.projectService.getSymlinkedProjects(scriptInfo);
1728            }
1729            // filter handles case when 'projects' is undefined
1730            projects = filter(projects, p => p.languageServiceEnabled && !p.isOrphan());
1731            if (!ignoreNoProjectError && (!projects || !projects.length) && !symLinkedProjects) {
1732                this.projectService.logErrorForScriptInfoNotFound(args.file ?? args.projectFileName);
1733                return Errors.ThrowNoProject();
1734            }
1735            return symLinkedProjects ? { projects: projects!, symLinkedProjects } : projects!; // TODO: GH#18217
1736        }
1737
1738        private getDefaultProject(args: protocol.FileRequestArgs) {
1739            if (args.projectFileName) {
1740                const project = this.getProject(args.projectFileName);
1741                if (project) {
1742                    return project;
1743                }
1744                if (!args.file) {
1745                    return Errors.ThrowNoProject();
1746                }
1747            }
1748            const info = this.projectService.getScriptInfo(args.file)!;
1749            return info.getDefaultProject();
1750        }
1751
1752        private getRenameLocations(args: protocol.RenameRequestArgs, simplifiedResult: boolean): protocol.RenameResponseBody | readonly RenameLocation[] {
1753            const file = toNormalizedPath(args.file);
1754            const position = this.getPositionInFile(args, file);
1755            const projects = this.getProjects(args);
1756            const defaultProject = this.getDefaultProject(args);
1757            const preferences = this.getPreferences(file);
1758            const renameInfo: protocol.RenameInfo = this.mapRenameInfo(
1759                defaultProject.getLanguageService().getRenameInfo(file, position, preferences), Debug.checkDefined(this.projectService.getScriptInfo(file)));
1760
1761            if (!renameInfo.canRename) return simplifiedResult ? { info: renameInfo, locs: [] } : [];
1762
1763            const locations = getRenameLocationsWorker(
1764                projects,
1765                defaultProject,
1766                { fileName: args.file, pos: position },
1767                !!args.findInStrings,
1768                !!args.findInComments,
1769                preferences,
1770            );
1771            if (!simplifiedResult) return locations;
1772            return { info: renameInfo, locs: this.toSpanGroups(locations) };
1773        }
1774
1775        private mapRenameInfo(info: RenameInfo, scriptInfo: ScriptInfo): protocol.RenameInfo {
1776            if (info.canRename) {
1777                const { canRename, fileToRename, displayName, fullDisplayName, kind, kindModifiers, triggerSpan } = info;
1778                return identity<protocol.RenameInfoSuccess>(
1779                    { canRename, fileToRename, displayName, fullDisplayName, kind, kindModifiers, triggerSpan: toProtocolTextSpan(triggerSpan, scriptInfo) });
1780            }
1781            else {
1782                return info;
1783            }
1784        }
1785
1786        private toSpanGroups(locations: readonly RenameLocation[]): readonly protocol.SpanGroup[] {
1787            const map = new Map<string, protocol.SpanGroup>();
1788            for (const { fileName, textSpan, contextSpan, originalContextSpan: _2, originalTextSpan: _, originalFileName: _1, ...prefixSuffixText } of locations) {
1789                let group = map.get(fileName);
1790                if (!group) map.set(fileName, group = { file: fileName, locs: [] });
1791                const scriptInfo = Debug.checkDefined(this.projectService.getScriptInfo(fileName));
1792                group.locs.push({ ...toProtocolTextSpanWithContext(textSpan, contextSpan, scriptInfo), ...prefixSuffixText });
1793            }
1794            return arrayFrom(map.values());
1795        }
1796
1797        private getReferences(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): protocol.ReferencesResponseBody | readonly ReferencedSymbol[] {
1798            const file = toNormalizedPath(args.file);
1799            const projects = this.getProjects(args);
1800            const position = this.getPositionInFile(args, file);
1801            const references = getReferencesWorker(
1802                projects,
1803                this.getDefaultProject(args),
1804                { fileName: args.file, pos: position },
1805                this.logger,
1806            );
1807
1808            if (!simplifiedResult) return references;
1809
1810            const preferences = this.getPreferences(file);
1811            const defaultProject = this.getDefaultProject(args);
1812            const scriptInfo = defaultProject.getScriptInfoForNormalizedPath(file)!;
1813            const nameInfo = defaultProject.getLanguageService().getQuickInfoAtPosition(file, position);
1814            const symbolDisplayString = nameInfo ? displayPartsToString(nameInfo.displayParts) : "";
1815            const nameSpan = nameInfo && nameInfo.textSpan;
1816            const symbolStartOffset = nameSpan ? scriptInfo.positionToLineOffset(nameSpan.start).offset : 0;
1817            const symbolName = nameSpan ? scriptInfo.getSnapshot().getText(nameSpan.start, textSpanEnd(nameSpan)) : "";
1818            const refs: readonly protocol.ReferencesResponseItem[] = flatMap(references, referencedSymbol => {
1819                return referencedSymbol.references.map(entry => referenceEntryToReferencesResponseItem(this.projectService, entry, preferences));
1820            });
1821            return { refs, symbolName, symbolStartOffset, symbolDisplayString };
1822        }
1823
1824        private getFileReferences(args: protocol.FileRequestArgs, simplifiedResult: boolean): protocol.FileReferencesResponseBody | readonly ReferenceEntry[] {
1825            const projects = this.getProjects(args);
1826            const fileName = args.file;
1827            const preferences = this.getPreferences(toNormalizedPath(fileName));
1828
1829            const references: ReferenceEntry[] = [];
1830            const seen = createDocumentSpanSet();
1831
1832            forEachProjectInProjects(projects, /*path*/ undefined, project => {
1833                if (project.getCancellationToken().isCancellationRequested()) return;
1834
1835                const projectOutputs = project.getLanguageService().getFileReferences(fileName);
1836                if (projectOutputs) {
1837                    for (const referenceEntry of projectOutputs) {
1838                        if (!seen.has(referenceEntry)) {
1839                            references.push(referenceEntry);
1840                            seen.add(referenceEntry);
1841                        }
1842                    }
1843                }
1844            });
1845
1846            if (!simplifiedResult) return references;
1847            const refs = references.map(entry => referenceEntryToReferencesResponseItem(this.projectService, entry, preferences));
1848            return {
1849                refs,
1850                symbolName: `"${args.file}"`
1851            };
1852        }
1853
1854        /**
1855         * @param fileName is the name of the file to be opened
1856         * @param fileContent is a version of the file content that is known to be more up to date than the one on disk
1857         */
1858        private openClientFile(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind, projectRootPath?: NormalizedPath) {
1859            this.projectService.openClientFileWithNormalizedPath(fileName, fileContent, scriptKind, /*hasMixedContent*/ false, projectRootPath);
1860        }
1861
1862        private getPosition(args: protocol.Location & { position?: number }, scriptInfo: ScriptInfo): number {
1863            return args.position !== undefined ? args.position : scriptInfo.lineOffsetToPosition(args.line, args.offset);
1864        }
1865
1866        private getPositionInFile(args: protocol.Location & { position?: number }, file: NormalizedPath): number {
1867            const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!;
1868            return this.getPosition(args, scriptInfo);
1869        }
1870
1871        private getFileAndProject(args: protocol.FileRequestArgs): FileAndProject {
1872            return this.getFileAndProjectWorker(args.file, args.projectFileName);
1873        }
1874
1875        private getFileAndLanguageServiceForSyntacticOperation(args: protocol.FileRequestArgs) {
1876            const { file, project } = this.getFileAndProject(args);
1877            return {
1878                file,
1879                languageService: project.getLanguageService(/*ensureSynchronized*/ false)
1880            };
1881        }
1882
1883        private getFileAndProjectWorker(uncheckedFileName: string, projectFileName: string | undefined): { file: NormalizedPath, project: Project } {
1884            const file = toNormalizedPath(uncheckedFileName);
1885            const project = this.getProject(projectFileName) || this.projectService.ensureDefaultProjectForFile(file);
1886            return { file, project };
1887        }
1888
1889        private getOutliningSpans(args: protocol.FileRequestArgs, simplifiedResult: boolean): protocol.OutliningSpan[] | OutliningSpan[] {
1890            const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
1891            const spans = languageService.getOutliningSpans(file);
1892            if (simplifiedResult) {
1893                const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!;
1894                return spans.map(s => ({
1895                    textSpan: toProtocolTextSpan(s.textSpan, scriptInfo),
1896                    hintSpan: toProtocolTextSpan(s.hintSpan, scriptInfo),
1897                    bannerText: s.bannerText,
1898                    autoCollapse: s.autoCollapse,
1899                    kind: s.kind
1900                }));
1901            }
1902            else {
1903                return spans;
1904            }
1905        }
1906
1907        private getTodoComments(args: protocol.TodoCommentRequestArgs) {
1908            const { file, project } = this.getFileAndProject(args);
1909            return project.getLanguageService().getTodoComments(file, args.descriptors);
1910        }
1911
1912        private getDocCommentTemplate(args: protocol.FileLocationRequestArgs) {
1913            const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
1914            const position = this.getPositionInFile(args, file);
1915            return languageService.getDocCommentTemplateAtPosition(file, position, this.getPreferences(file));
1916        }
1917
1918        private getSpanOfEnclosingComment(args: protocol.SpanOfEnclosingCommentRequestArgs) {
1919            const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
1920            const onlyMultiLine = args.onlyMultiLine;
1921            const position = this.getPositionInFile(args, file);
1922            return languageService.getSpanOfEnclosingComment(file, position, onlyMultiLine);
1923        }
1924
1925        private getIndentation(args: protocol.IndentationRequestArgs) {
1926            const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
1927            const position = this.getPositionInFile(args, file);
1928            const options = args.options ? convertFormatOptions(args.options) : this.getFormatOptions(file);
1929            const indentation = languageService.getIndentationAtPosition(file, position, options);
1930            return { position, indentation };
1931        }
1932
1933        private getBreakpointStatement(args: protocol.FileLocationRequestArgs) {
1934            const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
1935            const position = this.getPositionInFile(args, file);
1936            return languageService.getBreakpointStatementAtPosition(file, position);
1937        }
1938
1939        private getNameOrDottedNameSpan(args: protocol.FileLocationRequestArgs) {
1940            const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
1941            const position = this.getPositionInFile(args, file);
1942            return languageService.getNameOrDottedNameSpan(file, position, position);
1943        }
1944
1945        private isValidBraceCompletion(args: protocol.BraceCompletionRequestArgs) {
1946            const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
1947            const position = this.getPositionInFile(args, file);
1948            return languageService.isValidBraceCompletionAtPosition(file, position, args.openingBrace.charCodeAt(0));
1949        }
1950
1951        private getQuickInfoWorker(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): protocol.QuickInfoResponseBody | QuickInfo | undefined {
1952            const { file, project } = this.getFileAndProject(args);
1953            const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!;
1954            const quickInfo = project.getLanguageService().getQuickInfoAtPosition(file, this.getPosition(args, scriptInfo));
1955            if (!quickInfo) {
1956                return undefined;
1957            }
1958
1959            const useDisplayParts = !!this.getPreferences(file).displayPartsForJSDoc;
1960            if (simplifiedResult) {
1961                const displayString = displayPartsToString(quickInfo.displayParts);
1962                return {
1963                    kind: quickInfo.kind,
1964                    kindModifiers: quickInfo.kindModifiers,
1965                    start: scriptInfo.positionToLineOffset(quickInfo.textSpan.start),
1966                    end: scriptInfo.positionToLineOffset(textSpanEnd(quickInfo.textSpan)),
1967                    displayString,
1968                    documentation: useDisplayParts ? this.mapDisplayParts(quickInfo.documentation, project) : displayPartsToString(quickInfo.documentation),
1969                    tags: this.mapJSDocTagInfo(quickInfo.tags, project, useDisplayParts),
1970                };
1971            }
1972            else {
1973                return useDisplayParts ? quickInfo : {
1974                    ...quickInfo,
1975                    tags: this.mapJSDocTagInfo(quickInfo.tags, project, /*useDisplayParts*/ false) as JSDocTagInfo[]
1976                };
1977            }
1978        }
1979
1980        private getFormattingEditsForRange(args: protocol.FormatRequestArgs): protocol.CodeEdit[] | undefined {
1981            const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
1982            const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!;
1983
1984            const startPosition = scriptInfo.lineOffsetToPosition(args.line, args.offset);
1985            const endPosition = scriptInfo.lineOffsetToPosition(args.endLine, args.endOffset);
1986
1987            // TODO: avoid duplicate code (with formatonkey)
1988            const edits = languageService.getFormattingEditsForRange(file, startPosition, endPosition, this.getFormatOptions(file));
1989            if (!edits) {
1990                return undefined;
1991            }
1992
1993            return edits.map(edit => this.convertTextChangeToCodeEdit(edit, scriptInfo));
1994        }
1995
1996        private getFormattingEditsForRangeFull(args: protocol.FormatRequestArgs) {
1997            const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
1998            const options = args.options ? convertFormatOptions(args.options) : this.getFormatOptions(file);
1999            return languageService.getFormattingEditsForRange(file, args.position!, args.endPosition!, options); // TODO: GH#18217
2000        }
2001
2002        private getFormattingEditsForDocumentFull(args: protocol.FormatRequestArgs) {
2003            const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
2004            const options = args.options ? convertFormatOptions(args.options) : this.getFormatOptions(file);
2005            return languageService.getFormattingEditsForDocument(file, options);
2006        }
2007
2008        private getFormattingEditsAfterKeystrokeFull(args: protocol.FormatOnKeyRequestArgs) {
2009            const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
2010            const options = args.options ? convertFormatOptions(args.options) : this.getFormatOptions(file);
2011            return languageService.getFormattingEditsAfterKeystroke(file, args.position!, args.key, options); // TODO: GH#18217
2012        }
2013
2014        private getFormattingEditsAfterKeystroke(args: protocol.FormatOnKeyRequestArgs): protocol.CodeEdit[] | undefined {
2015            const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
2016            const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!;
2017            const position = scriptInfo.lineOffsetToPosition(args.line, args.offset);
2018            const formatOptions = this.getFormatOptions(file);
2019            const edits = languageService.getFormattingEditsAfterKeystroke(file, position, args.key,
2020                formatOptions);
2021            // Check whether we should auto-indent. This will be when
2022            // the position is on a line containing only whitespace.
2023            // This should leave the edits returned from
2024            // getFormattingEditsAfterKeystroke either empty or pertaining
2025            // only to the previous line.  If all this is true, then
2026            // add edits necessary to properly indent the current line.
2027            if ((args.key === "\n") && ((!edits) || (edits.length === 0) || allEditsBeforePos(edits, position))) {
2028                const { lineText, absolutePosition } = scriptInfo.getAbsolutePositionAndLineText(args.line);
2029                if (lineText && lineText.search("\\S") < 0) {
2030                    const preferredIndent = languageService.getIndentationAtPosition(file, position, formatOptions);
2031                    let hasIndent = 0;
2032                    let i: number, len: number;
2033                    for (i = 0, len = lineText.length; i < len; i++) {
2034                        if (lineText.charAt(i) === " ") {
2035                            hasIndent++;
2036                        }
2037                        else if (lineText.charAt(i) === "\t") {
2038                            hasIndent += formatOptions.tabSize!; // TODO: GH#18217
2039                        }
2040                        else {
2041                            break;
2042                        }
2043                    }
2044                    // i points to the first non whitespace character
2045                    if (preferredIndent !== hasIndent) {
2046                        const firstNoWhiteSpacePosition = absolutePosition + i;
2047                        edits.push({
2048                            span: createTextSpanFromBounds(absolutePosition, firstNoWhiteSpacePosition),
2049                            newText: formatting.getIndentationString(preferredIndent, formatOptions)
2050                        });
2051                    }
2052                }
2053            }
2054
2055            if (!edits) {
2056                return undefined;
2057            }
2058
2059            return edits.map((edit) => {
2060                return {
2061                    start: scriptInfo.positionToLineOffset(edit.span.start),
2062                    end: scriptInfo.positionToLineOffset(textSpanEnd(edit.span)),
2063                    newText: edit.newText ? edit.newText : ""
2064                };
2065            });
2066        }
2067
2068        private getCompletions(args: protocol.CompletionsRequestArgs, kind: protocol.CommandTypes.CompletionInfo | protocol.CommandTypes.Completions | protocol.CommandTypes.CompletionsFull): WithMetadata<readonly protocol.CompletionEntry[]> | protocol.CompletionInfo | CompletionInfo | undefined {
2069            const { file, project } = this.getFileAndProject(args);
2070            const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!;
2071            const position = this.getPosition(args, scriptInfo);
2072
2073            const completions = project.getLanguageService().getCompletionsAtPosition(
2074                file,
2075                position,
2076                {
2077                    ...convertUserPreferences(this.getPreferences(file)),
2078                    triggerCharacter: args.triggerCharacter,
2079                    triggerKind: args.triggerKind as CompletionTriggerKind | undefined,
2080                    includeExternalModuleExports: args.includeExternalModuleExports,
2081                    includeInsertTextCompletions: args.includeInsertTextCompletions,
2082                },
2083                project.projectService.getFormatCodeOptions(file),
2084            );
2085            if (completions === undefined) return undefined;
2086
2087            if (kind === protocol.CommandTypes.CompletionsFull) return completions;
2088
2089            const prefix = args.prefix || "";
2090            const entries = mapDefined<CompletionEntry, protocol.CompletionEntry>(completions.entries, entry => {
2091                if (completions.isMemberCompletion || startsWith(entry.name.toLowerCase(), prefix.toLowerCase())) {
2092                    const {
2093                        name,
2094                        kind,
2095                        kindModifiers,
2096                        sortText,
2097                        insertText,
2098                        replacementSpan,
2099                        hasAction,
2100                        source,
2101                        sourceDisplay,
2102                        labelDetails,
2103                        isSnippet,
2104                        isRecommended,
2105                        isPackageJsonImport,
2106                        isImportStatementCompletion,
2107                        jsDoc,
2108                        displayParts,
2109                        data } = entry;
2110                    const convertedSpan = replacementSpan ? toProtocolTextSpan(replacementSpan, scriptInfo) : undefined;
2111                    // Use `hasAction || undefined` to avoid serializing `false`.
2112                    return {
2113                        name,
2114                        kind,
2115                        kindModifiers,
2116                        sortText,
2117                        insertText,
2118                        replacementSpan: convertedSpan,
2119                        isSnippet,
2120                        hasAction: hasAction || undefined,
2121                        source,
2122                        sourceDisplay,
2123                        labelDetails,
2124                        isRecommended,
2125                        isPackageJsonImport,
2126                        isImportStatementCompletion,
2127                        jsDoc,
2128                        displayParts,
2129                        data
2130                    };
2131                }
2132            });
2133
2134            if (kind === protocol.CommandTypes.Completions) {
2135                if (completions.metadata) (entries as WithMetadata<readonly protocol.CompletionEntry[]>).metadata = completions.metadata;
2136                return entries;
2137            }
2138
2139            const res: protocol.CompletionInfo = {
2140                ...completions,
2141                optionalReplacementSpan: completions.optionalReplacementSpan && toProtocolTextSpan(completions.optionalReplacementSpan, scriptInfo),
2142                entries,
2143            };
2144            return res;
2145        }
2146
2147        private getCompletionEntryDetails(args: protocol.CompletionDetailsRequestArgs, fullResult: boolean): readonly protocol.CompletionEntryDetails[] | readonly CompletionEntryDetails[] {
2148            const { file, project } = this.getFileAndProject(args);
2149            const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!;
2150            const position = this.getPosition(args, scriptInfo);
2151            const formattingOptions = project.projectService.getFormatCodeOptions(file);
2152            const useDisplayParts = !!this.getPreferences(file).displayPartsForJSDoc;
2153
2154            const result = mapDefined(args.entryNames, entryName => {
2155                const { name, source, data } = typeof entryName === "string" ? { name: entryName, source: undefined, data: undefined } : entryName;
2156                return project.getLanguageService().getCompletionEntryDetails(file, position, name, formattingOptions, source, this.getPreferences(file), data ? cast(data, isCompletionEntryData) : undefined);
2157            });
2158            return fullResult
2159                ? (useDisplayParts ? result : result.map(details => ({ ...details, tags: this.mapJSDocTagInfo(details.tags, project, /*richResponse*/ false) as JSDocTagInfo[] })))
2160                : result.map(details => ({
2161                    ...details,
2162                    codeActions: map(details.codeActions, action => this.mapCodeAction(action)),
2163                    documentation: this.mapDisplayParts(details.documentation, project),
2164                    tags: this.mapJSDocTagInfo(details.tags, project, useDisplayParts),
2165                }));
2166        }
2167
2168        private getCompileOnSaveAffectedFileList(args: protocol.FileRequestArgs): readonly protocol.CompileOnSaveAffectedFileListSingleProject[] {
2169            const projects = this.getProjects(args, /*getScriptInfoEnsuringProjectsUptoDate*/ true, /*ignoreNoProjectError*/ true);
2170            const info = this.projectService.getScriptInfo(args.file);
2171            if (!info) {
2172                return emptyArray;
2173            }
2174
2175            return combineProjectOutput(
2176                info,
2177                path => this.projectService.getScriptInfoForPath(path)!,
2178                projects,
2179                (project, info) => {
2180                    if (!project.compileOnSaveEnabled || !project.languageServiceEnabled || project.isOrphan()) {
2181                        return undefined;
2182                    }
2183
2184                    const compilationSettings = project.getCompilationSettings();
2185
2186                    if (!!compilationSettings.noEmit || isDeclarationFileName(info.fileName) && !dtsChangeCanAffectEmit(compilationSettings)) {
2187                        // avoid triggering emit when a change is made in a .d.ts when declaration emit and decorator metadata emit are disabled
2188                        return undefined;
2189                    }
2190
2191                    return {
2192                        projectFileName: project.getProjectName(),
2193                        fileNames: project.getCompileOnSaveAffectedFileList(info),
2194                        projectUsesOutFile: !!outFile(compilationSettings)
2195                    };
2196                }
2197            );
2198        }
2199
2200        private emitFile(args: protocol.CompileOnSaveEmitFileRequestArgs): boolean | protocol.EmitResult | EmitResult {
2201            const { file, project } = this.getFileAndProject(args);
2202            if (!project) {
2203                Errors.ThrowNoProject();
2204            }
2205            if (!project.languageServiceEnabled) {
2206                return args.richResponse ? { emitSkipped: true, diagnostics: [] } : false;
2207            }
2208            const scriptInfo = project.getScriptInfo(file)!;
2209            const { emitSkipped, diagnostics } = project.emitFile(scriptInfo, (path, data, writeByteOrderMark) => this.host.writeFile(path, data, writeByteOrderMark));
2210            return args.richResponse ?
2211                {
2212                    emitSkipped,
2213                    diagnostics: args.includeLinePosition ?
2214                        this.convertToDiagnosticsWithLinePositionFromDiagnosticFile(diagnostics) :
2215                        diagnostics.map(d => formatDiagnosticToProtocol(d, /*includeFileName*/ true))
2216                } :
2217                !emitSkipped;
2218        }
2219
2220        private getSignatureHelpItems(args: protocol.SignatureHelpRequestArgs, simplifiedResult: boolean): protocol.SignatureHelpItems | SignatureHelpItems | undefined {
2221            const { file, project } = this.getFileAndProject(args);
2222            const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!;
2223            const position = this.getPosition(args, scriptInfo);
2224            const helpItems = project.getLanguageService().getSignatureHelpItems(file, position, args);
2225            const useDisplayParts = !!this.getPreferences(file).displayPartsForJSDoc;
2226            if (helpItems && simplifiedResult) {
2227                const span = helpItems.applicableSpan;
2228                return {
2229                    ...helpItems,
2230                    applicableSpan: {
2231                        start: scriptInfo.positionToLineOffset(span.start),
2232                        end: scriptInfo.positionToLineOffset(span.start + span.length)
2233                    },
2234                    items: this.mapSignatureHelpItems(helpItems.items, project, useDisplayParts),
2235                };
2236            }
2237            else if (useDisplayParts || !helpItems) {
2238                return helpItems;
2239            }
2240            else {
2241                return {
2242                    ...helpItems,
2243                    items: helpItems.items.map(item => ({ ...item, tags: this.mapJSDocTagInfo(item.tags, project, /*richResponse*/ false) as JSDocTagInfo[] }))
2244                };
2245            }
2246        }
2247
2248        private toPendingErrorCheck(uncheckedFileName: string): PendingErrorCheck | undefined {
2249            const fileName = toNormalizedPath(uncheckedFileName);
2250            const project = this.projectService.tryGetDefaultProjectForFile(fileName);
2251            return project && { fileName, project };
2252        }
2253
2254        private getDiagnostics(next: NextStep, delay: number, fileNames: string[]): void {
2255            if (this.suppressDiagnosticEvents) {
2256                return;
2257            }
2258
2259            if (fileNames.length > 0) {
2260                this.updateErrorCheck(next, fileNames, delay);
2261            }
2262        }
2263
2264        private change(args: protocol.ChangeRequestArgs) {
2265            const scriptInfo = this.projectService.getScriptInfo(args.file)!;
2266            Debug.assert(!!scriptInfo);
2267            const start = scriptInfo.lineOffsetToPosition(args.line, args.offset);
2268            const end = scriptInfo.lineOffsetToPosition(args.endLine, args.endOffset);
2269            if (start >= 0) {
2270                this.changeSeq++;
2271                this.projectService.applyChangesToFile(scriptInfo, singleIterator({
2272                    span: { start, length: end - start },
2273                    newText: args.insertString! // TODO: GH#18217
2274                }));
2275            }
2276        }
2277
2278        private reload(args: protocol.ReloadRequestArgs, reqSeq: number) {
2279            const file = toNormalizedPath(args.file);
2280            const tempFileName = args.tmpfile === undefined ? undefined : toNormalizedPath(args.tmpfile);
2281            const info = this.projectService.getScriptInfoForNormalizedPath(file);
2282            if (info) {
2283                this.changeSeq++;
2284                // make sure no changes happen before this one is finished
2285                if (info.reloadFromFile(tempFileName)) {
2286                    this.doOutput(/*info*/ undefined, CommandNames.Reload, reqSeq, /*success*/ true);
2287                }
2288            }
2289        }
2290
2291        private saveToTmp(fileName: string, tempFileName: string) {
2292            const scriptInfo = this.projectService.getScriptInfo(fileName);
2293            if (scriptInfo) {
2294                scriptInfo.saveTo(tempFileName);
2295            }
2296        }
2297
2298        private closeClientFile(fileName: string) {
2299            if (!fileName) {
2300                return;
2301            }
2302            const file = normalizePath(fileName);
2303            this.projectService.closeClientFile(file);
2304        }
2305
2306        private mapLocationNavigationBarItems(items: NavigationBarItem[], scriptInfo: ScriptInfo): protocol.NavigationBarItem[] {
2307            return map(items, item => ({
2308                text: item.text,
2309                kind: item.kind,
2310                kindModifiers: item.kindModifiers,
2311                spans: item.spans.map(span => toProtocolTextSpan(span, scriptInfo)),
2312                childItems: this.mapLocationNavigationBarItems(item.childItems, scriptInfo),
2313                indent: item.indent
2314            }));
2315        }
2316
2317        private getNavigationBarItems(args: protocol.FileRequestArgs, simplifiedResult: boolean): protocol.NavigationBarItem[] | NavigationBarItem[] | undefined {
2318            const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
2319            const items = languageService.getNavigationBarItems(file);
2320            return !items
2321                ? undefined
2322                : simplifiedResult
2323                    ? this.mapLocationNavigationBarItems(items, this.projectService.getScriptInfoForNormalizedPath(file)!)
2324                    : items;
2325        }
2326
2327        private toLocationNavigationTree(tree: NavigationTree, scriptInfo: ScriptInfo): protocol.NavigationTree {
2328            return {
2329                text: tree.text,
2330                kind: tree.kind,
2331                kindModifiers: tree.kindModifiers,
2332                spans: tree.spans.map(span => toProtocolTextSpan(span, scriptInfo)),
2333                nameSpan: tree.nameSpan && toProtocolTextSpan(tree.nameSpan, scriptInfo),
2334                childItems: map(tree.childItems, item => this.toLocationNavigationTree(item, scriptInfo))
2335            };
2336        }
2337
2338        private getNavigationTree(args: protocol.FileRequestArgs, simplifiedResult: boolean): protocol.NavigationTree | NavigationTree | undefined {
2339            const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
2340            const tree = languageService.getNavigationTree(file);
2341            return !tree
2342                ? undefined
2343                : simplifiedResult
2344                    ? this.toLocationNavigationTree(tree, this.projectService.getScriptInfoForNormalizedPath(file)!)
2345                    : tree;
2346        }
2347
2348        private getNavigateToItems(args: protocol.NavtoRequestArgs, simplifiedResult: boolean): readonly protocol.NavtoItem[] | readonly NavigateToItem[] {
2349            const full = this.getFullNavigateToItems(args);
2350            return !simplifiedResult ?
2351                flatMap(full, ({ navigateToItems }) => navigateToItems) :
2352                flatMap(
2353                    full,
2354                    ({ project, navigateToItems }) => navigateToItems.map(navItem => {
2355                        const scriptInfo = project.getScriptInfo(navItem.fileName)!;
2356                        const bakedItem: protocol.NavtoItem = {
2357                            name: navItem.name,
2358                            kind: navItem.kind,
2359                            kindModifiers: navItem.kindModifiers,
2360                            isCaseSensitive: navItem.isCaseSensitive,
2361                            matchKind: navItem.matchKind,
2362                            file: navItem.fileName,
2363                            start: scriptInfo.positionToLineOffset(navItem.textSpan.start),
2364                            end: scriptInfo.positionToLineOffset(textSpanEnd(navItem.textSpan))
2365                        };
2366                        if (navItem.kindModifiers && (navItem.kindModifiers !== "")) {
2367                            bakedItem.kindModifiers = navItem.kindModifiers;
2368                        }
2369                        if (navItem.containerName && (navItem.containerName.length > 0)) {
2370                            bakedItem.containerName = navItem.containerName;
2371                        }
2372                        if (navItem.containerKind && (navItem.containerKind.length > 0)) {
2373                            bakedItem.containerKind = navItem.containerKind;
2374                        }
2375                        return bakedItem;
2376                    })
2377                );
2378        }
2379
2380        private getFullNavigateToItems(args: protocol.NavtoRequestArgs): ProjectNavigateToItems[] {
2381            const { currentFileOnly, searchValue, maxResultCount, projectFileName } = args;
2382
2383            if (currentFileOnly) {
2384                Debug.assertIsDefined(args.file);
2385                const { file, project } = this.getFileAndProject(args as protocol.FileRequestArgs);
2386                return [{ project, navigateToItems: project.getLanguageService().getNavigateToItems(searchValue, maxResultCount, file) }];
2387            }
2388
2389            const outputs: ProjectNavigateToItems[] = [];
2390
2391            // This is effectively a hashset with `name` as the custom hash and `navigateToItemIsEqualTo` as the custom equals.
2392            // `name` is a very cheap hash function, but we could incorporate other properties to reduce collisions.
2393            const seenItems = new Map<string, NavigateToItem[]>(); // name to items with that name
2394
2395            if (!args.file && !projectFileName) {
2396                // VS Code's `Go to symbol in workspaces` sends request like this by default.
2397                // There's a setting to have it send a file name (reverting to older behavior).
2398
2399                // TODO (https://github.com/microsoft/TypeScript/issues/47839)
2400                // This appears to have been intended to search all projects but, in practice, it seems to only search
2401                // those that are downstream from already-loaded projects.
2402                // Filtering by !isSourceOfProjectReferenceRedirect is new, but seems appropriate and consistent with
2403                // the case below.
2404                this.projectService.loadAncestorProjectTree();
2405                this.projectService.forEachEnabledProject(project => addItemsForProject(project));
2406            }
2407            else {
2408                // VS's `Go to symbol` sends requests with just a project and doesn't want cascading since it will
2409                // send a separate request for each project of interest
2410
2411                // TODO (https://github.com/microsoft/TypeScript/issues/47839)
2412                // This doesn't really make sense unless it's a single project matching `projectFileName`
2413                const projects = this.getProjects(args as protocol.FileRequestArgs);
2414                forEachProjectInProjects(projects, /*path*/ undefined, project => addItemsForProject(project));
2415            }
2416
2417            return outputs;
2418
2419            // Mutates `outputs`
2420            function addItemsForProject(project: Project) {
2421                const projectItems = project.getLanguageService().getNavigateToItems(searchValue, maxResultCount, /*filename*/ undefined, /*excludeDts*/ project.isNonTsProject());
2422                const unseenItems = filter(projectItems, item => tryAddSeenItem(item) && !getMappedLocationForProject(documentSpanLocation(item), project));
2423                if (unseenItems.length) {
2424                    outputs.push({ project, navigateToItems: unseenItems });
2425                }
2426            }
2427
2428            // Returns true if the item had not been seen before
2429            // Mutates `seenItems`
2430            function tryAddSeenItem(item: NavigateToItem) {
2431                const name = item.name;
2432                if (!seenItems.has(name)) {
2433                    seenItems.set(name, [item]);
2434                    return true;
2435                }
2436
2437                const seen = seenItems.get(name)!;
2438                for (const seenItem of seen) {
2439                    if (navigateToItemIsEqualTo(seenItem, item)) {
2440                        return false;
2441                    }
2442                }
2443
2444                seen.push(item);
2445                return true;
2446            }
2447
2448            function navigateToItemIsEqualTo(a: NavigateToItem, b: NavigateToItem): boolean {
2449                if (a === b) {
2450                    return true;
2451                }
2452                if (!a || !b) {
2453                    return false;
2454                }
2455                return a.containerKind === b.containerKind &&
2456                    a.containerName === b.containerName &&
2457                    a.fileName === b.fileName &&
2458                    a.isCaseSensitive === b.isCaseSensitive &&
2459                    a.kind === b.kind &&
2460                    a.kindModifiers === b.kindModifiers &&
2461                    a.matchKind === b.matchKind &&
2462                    a.name === b.name &&
2463                    a.textSpan.start === b.textSpan.start &&
2464                    a.textSpan.length === b.textSpan.length;
2465            }
2466        }
2467
2468        private getSupportedCodeFixes(): string[] {
2469            return getSupportedCodeFixes();
2470        }
2471
2472        private isLocation(locationOrSpan: protocol.FileLocationOrRangeRequestArgs): locationOrSpan is protocol.FileLocationRequestArgs {
2473            return (locationOrSpan as protocol.FileLocationRequestArgs).line !== undefined;
2474        }
2475
2476        private extractPositionOrRange(args: protocol.FileLocationOrRangeRequestArgs, scriptInfo: ScriptInfo): number | TextRange {
2477            let position: number | undefined;
2478            let textRange: TextRange | undefined;
2479            if (this.isLocation(args)) {
2480                position = getPosition(args);
2481            }
2482            else {
2483                textRange = this.getRange(args, scriptInfo);
2484            }
2485            return Debug.checkDefined(position === undefined ? textRange : position);
2486
2487            function getPosition(loc: protocol.FileLocationRequestArgs) {
2488                return loc.position !== undefined ? loc.position : scriptInfo.lineOffsetToPosition(loc.line, loc.offset);
2489            }
2490        }
2491
2492        private getRange(args: protocol.FileRangeRequestArgs, scriptInfo: ScriptInfo): TextRange {
2493            const { startPosition, endPosition } = this.getStartAndEndPosition(args, scriptInfo);
2494
2495            return { pos: startPosition, end: endPosition };
2496        }
2497
2498        private getApplicableRefactors(args: protocol.GetApplicableRefactorsRequestArgs): protocol.ApplicableRefactorInfo[] {
2499            const { file, project } = this.getFileAndProject(args);
2500            const scriptInfo = project.getScriptInfoForNormalizedPath(file)!;
2501            return project.getLanguageService().getApplicableRefactors(file, this.extractPositionOrRange(args, scriptInfo), this.getPreferences(file), args.triggerReason, args.kind);
2502        }
2503
2504        private getEditsForRefactor(args: protocol.GetEditsForRefactorRequestArgs, simplifiedResult: boolean): RefactorEditInfo | protocol.RefactorEditInfo {
2505            const { file, project } = this.getFileAndProject(args);
2506            const scriptInfo = project.getScriptInfoForNormalizedPath(file)!;
2507            const result = project.getLanguageService().getEditsForRefactor(
2508                file,
2509                this.getFormatOptions(file),
2510                this.extractPositionOrRange(args, scriptInfo),
2511                args.refactor,
2512                args.action,
2513                this.getPreferences(file),
2514            );
2515
2516            if (result === undefined) {
2517                return {
2518                    edits: []
2519                };
2520            }
2521
2522            if (simplifiedResult) {
2523                const { renameFilename, renameLocation, edits } = result;
2524                let mappedRenameLocation: protocol.Location | undefined;
2525                if (renameFilename !== undefined && renameLocation !== undefined) {
2526                    const renameScriptInfo = project.getScriptInfoForNormalizedPath(toNormalizedPath(renameFilename))!;
2527                    mappedRenameLocation = getLocationInNewDocument(getSnapshotText(renameScriptInfo.getSnapshot()), renameFilename, renameLocation, edits);
2528                }
2529                return { renameLocation: mappedRenameLocation, renameFilename, edits: this.mapTextChangesToCodeEdits(edits) };
2530            }
2531            else {
2532                return result;
2533            }
2534        }
2535
2536        private organizeImports(args: protocol.OrganizeImportsRequestArgs, simplifiedResult: boolean): readonly protocol.FileCodeEdits[] | readonly FileTextChanges[] {
2537            Debug.assert(args.scope.type === "file");
2538            const { file, project } = this.getFileAndProject(args.scope.args);
2539            const changes = project.getLanguageService().organizeImports(
2540                {
2541                    fileName: file,
2542                    mode: args.mode as OrganizeImportsMode | undefined ?? (args.skipDestructiveCodeActions ? OrganizeImportsMode.SortAndCombine : undefined),
2543                    type: "file",
2544                },
2545                this.getFormatOptions(file),
2546                this.getPreferences(file)
2547            );
2548            if (simplifiedResult) {
2549                return this.mapTextChangesToCodeEdits(changes);
2550            }
2551            else {
2552                return changes;
2553            }
2554        }
2555
2556        private getEditsForFileRename(args: protocol.GetEditsForFileRenameRequestArgs, simplifiedResult: boolean): readonly protocol.FileCodeEdits[] | readonly FileTextChanges[] {
2557            const oldPath = toNormalizedPath(args.oldFilePath);
2558            const newPath = toNormalizedPath(args.newFilePath);
2559            const formatOptions = this.getHostFormatOptions();
2560            const preferences = this.getHostPreferences();
2561
2562
2563            const seenFiles = new Set<string>();
2564            const textChanges: FileTextChanges[] = [];
2565            // TODO (https://github.com/microsoft/TypeScript/issues/47839)
2566            // This appears to have been intended to search all projects but, in practice, it seems to only search
2567            // those that are downstream from already-loaded projects.
2568            this.projectService.loadAncestorProjectTree();
2569            this.projectService.forEachEnabledProject(project => {
2570                const projectTextChanges = project.getLanguageService().getEditsForFileRename(oldPath, newPath, formatOptions, preferences);
2571                const projectFiles: string[] = [];
2572                for (const textChange of projectTextChanges) {
2573                    if (!seenFiles.has(textChange.fileName)) {
2574                        textChanges.push(textChange);
2575                        projectFiles.push(textChange.fileName);
2576                    }
2577                }
2578                for (const file of projectFiles) {
2579                    seenFiles.add(file);
2580                }
2581            });
2582
2583            return simplifiedResult ? textChanges.map(c => this.mapTextChangeToCodeEdit(c)) : textChanges;
2584        }
2585
2586        private getCodeFixes(args: protocol.CodeFixRequestArgs, simplifiedResult: boolean): readonly protocol.CodeFixAction[] | readonly CodeFixAction[] | undefined {
2587            const { file, project } = this.getFileAndProject(args);
2588
2589            const scriptInfo = project.getScriptInfoForNormalizedPath(file)!;
2590            const { startPosition, endPosition } = this.getStartAndEndPosition(args, scriptInfo);
2591
2592            let codeActions: readonly CodeFixAction[];
2593            try {
2594                codeActions = project.getLanguageService().getCodeFixesAtPosition(file, startPosition, endPosition, args.errorCodes, this.getFormatOptions(file), this.getPreferences(file));
2595            }
2596            catch(e) {
2597                const ls = project.getLanguageService();
2598                const existingDiagCodes = [
2599                    ...ls.getSyntacticDiagnostics(file),
2600                    ...ls.getSemanticDiagnostics(file),
2601                    ...ls.getSuggestionDiagnostics(file)
2602                ].map(d =>
2603                    decodedTextSpanIntersectsWith(startPosition, endPosition - startPosition, d.start!, d.length!)
2604                    && d.code);
2605                const badCode = args.errorCodes.find(c => !existingDiagCodes.includes(c));
2606                if (badCode !== undefined) {
2607                    e.message = `BADCLIENT: Bad error code, ${badCode} not found in range ${startPosition}..${endPosition} (found: ${existingDiagCodes.join(", ")}); could have caused this error:\n${e.message}`;
2608                }
2609                throw e;
2610            }
2611            return simplifiedResult ? codeActions.map(codeAction => this.mapCodeFixAction(codeAction)) : codeActions;
2612        }
2613
2614        private getCombinedCodeFix({ scope, fixId }: protocol.GetCombinedCodeFixRequestArgs, simplifiedResult: boolean): protocol.CombinedCodeActions | CombinedCodeActions {
2615            Debug.assert(scope.type === "file");
2616            const { file, project } = this.getFileAndProject(scope.args);
2617            const res = project.getLanguageService().getCombinedCodeFix({ type: "file", fileName: file }, fixId, this.getFormatOptions(file), this.getPreferences(file));
2618            if (simplifiedResult) {
2619                return { changes: this.mapTextChangesToCodeEdits(res.changes), commands: res.commands };
2620            }
2621            else {
2622                return res;
2623            }
2624        }
2625
2626        private applyCodeActionCommand(args: protocol.ApplyCodeActionCommandRequestArgs): {} {
2627            const commands = args.command as CodeActionCommand | CodeActionCommand[]; // They should be sending back the command we sent them.
2628            for (const command of toArray(commands)) {
2629                const { file, project } = this.getFileAndProject(command);
2630                project.getLanguageService().applyCodeActionCommand(command, this.getFormatOptions(file)).then(
2631                    _result => { /* TODO: GH#20447 report success message? */ },
2632                    _error => { /* TODO: GH#20447 report errors */ });
2633            }
2634            return {};
2635        }
2636
2637        private getStartAndEndPosition(args: protocol.FileRangeRequestArgs, scriptInfo: ScriptInfo) {
2638            let startPosition: number | undefined, endPosition: number | undefined;
2639            if (args.startPosition !== undefined) {
2640                startPosition = args.startPosition;
2641            }
2642            else {
2643                startPosition = scriptInfo.lineOffsetToPosition(args.startLine, args.startOffset);
2644                // save the result so we don't always recompute
2645                args.startPosition = startPosition;
2646            }
2647
2648            if (args.endPosition !== undefined) {
2649                endPosition = args.endPosition;
2650            }
2651            else {
2652                endPosition = scriptInfo.lineOffsetToPosition(args.endLine, args.endOffset);
2653                args.endPosition = endPosition;
2654            }
2655
2656            return { startPosition, endPosition };
2657        }
2658
2659        private mapCodeAction({ description, changes, commands }: CodeAction): protocol.CodeAction {
2660            return { description, changes: this.mapTextChangesToCodeEdits(changes), commands };
2661        }
2662
2663        private mapCodeFixAction({ fixName, description, changes, commands, fixId, fixAllDescription }: CodeFixAction): protocol.CodeFixAction {
2664            return { fixName, description, changes: this.mapTextChangesToCodeEdits(changes), commands, fixId, fixAllDescription };
2665        }
2666
2667        private mapTextChangesToCodeEdits(textChanges: readonly FileTextChanges[]): protocol.FileCodeEdits[] {
2668            return textChanges.map(change => this.mapTextChangeToCodeEdit(change));
2669        }
2670
2671        private mapTextChangeToCodeEdit(textChanges: FileTextChanges): protocol.FileCodeEdits {
2672            const scriptInfo = this.projectService.getScriptInfoOrConfig(textChanges.fileName);
2673            if (!!textChanges.isNewFile === !!scriptInfo) {
2674                if (!scriptInfo) { // and !isNewFile
2675                    this.projectService.logErrorForScriptInfoNotFound(textChanges.fileName);
2676                }
2677                Debug.fail("Expected isNewFile for (only) new files. " + JSON.stringify({ isNewFile: !!textChanges.isNewFile, hasScriptInfo: !!scriptInfo }));
2678            }
2679            return scriptInfo
2680                ? { fileName: textChanges.fileName, textChanges: textChanges.textChanges.map(textChange => convertTextChangeToCodeEdit(textChange, scriptInfo)) }
2681                : convertNewFileTextChangeToCodeEdit(textChanges);
2682        }
2683
2684        private convertTextChangeToCodeEdit(change: TextChange, scriptInfo: ScriptInfo): protocol.CodeEdit {
2685            return {
2686                start: scriptInfo.positionToLineOffset(change.span.start),
2687                end: scriptInfo.positionToLineOffset(change.span.start + change.span.length),
2688                newText: change.newText ? change.newText : ""
2689            };
2690        }
2691
2692        private getBraceMatching(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): protocol.TextSpan[] | TextSpan[] | undefined {
2693            const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
2694            const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!;
2695            const position = this.getPosition(args, scriptInfo);
2696
2697            const spans = languageService.getBraceMatchingAtPosition(file, position);
2698            return !spans
2699                ? undefined
2700                : simplifiedResult
2701                    ? spans.map(span => toProtocolTextSpan(span, scriptInfo))
2702                    : spans;
2703        }
2704
2705        private getDiagnosticsForProject(next: NextStep, delay: number, fileName: string): void {
2706            if (this.suppressDiagnosticEvents) {
2707                return;
2708            }
2709
2710            const { fileNames, languageServiceDisabled } = this.getProjectInfoWorker(fileName, /*projectFileName*/ undefined, /*needFileNameList*/ true, /*excludeConfigFiles*/ true);
2711            if (languageServiceDisabled) {
2712                return;
2713            }
2714
2715            // No need to analyze lib.d.ts
2716            const fileNamesInProject = fileNames!.filter(value => !stringContains(value, "lib.d.ts")); // TODO: GH#18217
2717            if (fileNamesInProject.length === 0) {
2718                return;
2719            }
2720
2721            // Sort the file name list to make the recently touched files come first
2722            const highPriorityFiles: NormalizedPath[] = [];
2723            const mediumPriorityFiles: NormalizedPath[] = [];
2724            const lowPriorityFiles: NormalizedPath[] = [];
2725            const veryLowPriorityFiles: NormalizedPath[] = [];
2726            const normalizedFileName = toNormalizedPath(fileName);
2727            const project = this.projectService.ensureDefaultProjectForFile(normalizedFileName);
2728            for (const fileNameInProject of fileNamesInProject) {
2729                if (this.getCanonicalFileName(fileNameInProject) === this.getCanonicalFileName(fileName)) {
2730                    highPriorityFiles.push(fileNameInProject);
2731                }
2732                else {
2733                    const info = this.projectService.getScriptInfo(fileNameInProject)!; // TODO: GH#18217
2734                    if (!info.isScriptOpen()) {
2735                        if (isDeclarationFileName(fileNameInProject)) {
2736                            veryLowPriorityFiles.push(fileNameInProject);
2737                        }
2738                        else {
2739                            lowPriorityFiles.push(fileNameInProject);
2740                        }
2741                    }
2742                    else {
2743                        mediumPriorityFiles.push(fileNameInProject);
2744                    }
2745                }
2746            }
2747
2748            const sortedFiles = [...highPriorityFiles, ...mediumPriorityFiles, ...lowPriorityFiles, ...veryLowPriorityFiles];
2749            const checkList = sortedFiles.map(fileName => ({ fileName, project }));
2750            // Project level error analysis runs on background files too, therefore
2751            // doesn't require the file to be opened
2752            this.updateErrorCheck(next, checkList, delay, /*requireOpen*/ false);
2753        }
2754
2755        private configurePlugin(args: protocol.ConfigurePluginRequestArguments) {
2756            this.projectService.configurePlugin(args);
2757        }
2758
2759        private getSmartSelectionRange(args: protocol.SelectionRangeRequestArgs, simplifiedResult: boolean) {
2760            const { locations } = args;
2761            const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
2762            const scriptInfo = Debug.checkDefined(this.projectService.getScriptInfo(file));
2763
2764            return map(locations, location => {
2765                const pos = this.getPosition(location, scriptInfo);
2766                const selectionRange = languageService.getSmartSelectionRange(file, pos);
2767                return simplifiedResult ? this.mapSelectionRange(selectionRange, scriptInfo) : selectionRange;
2768            });
2769        }
2770
2771        private toggleLineComment(args: protocol.FileRangeRequestArgs, simplifiedResult: boolean): TextChange[] | protocol.CodeEdit[] {
2772            const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
2773            const scriptInfo = this.projectService.getScriptInfo(file)!;
2774            const textRange = this.getRange(args, scriptInfo);
2775
2776            const textChanges = languageService.toggleLineComment(file, textRange);
2777
2778            if (simplifiedResult) {
2779                const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!;
2780
2781                return textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, scriptInfo));
2782            }
2783
2784            return textChanges;
2785        }
2786
2787        private toggleMultilineComment(args: protocol.FileRangeRequestArgs, simplifiedResult: boolean): TextChange[] | protocol.CodeEdit[] {
2788            const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
2789            const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!;
2790            const textRange = this.getRange(args, scriptInfo);
2791
2792            const textChanges = languageService.toggleMultilineComment(file, textRange);
2793
2794            if (simplifiedResult) {
2795                const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!;
2796
2797                return textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, scriptInfo));
2798            }
2799
2800            return textChanges;
2801        }
2802
2803        private commentSelection(args: protocol.FileRangeRequestArgs, simplifiedResult: boolean): TextChange[] | protocol.CodeEdit[] {
2804            const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
2805            const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!;
2806            const textRange = this.getRange(args, scriptInfo);
2807
2808            const textChanges = languageService.commentSelection(file, textRange);
2809
2810            if (simplifiedResult) {
2811                const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!;
2812
2813                return textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, scriptInfo));
2814            }
2815
2816            return textChanges;
2817        }
2818
2819        private uncommentSelection(args: protocol.FileRangeRequestArgs, simplifiedResult: boolean): TextChange[] | protocol.CodeEdit[] {
2820            const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
2821            const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!;
2822            const textRange = this.getRange(args, scriptInfo);
2823
2824            const textChanges = languageService.uncommentSelection(file, textRange);
2825
2826            if (simplifiedResult) {
2827                const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!;
2828
2829                return textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, scriptInfo));
2830            }
2831
2832            return textChanges;
2833        }
2834
2835        private mapSelectionRange(selectionRange: SelectionRange, scriptInfo: ScriptInfo): protocol.SelectionRange {
2836            const result: protocol.SelectionRange = {
2837                textSpan: toProtocolTextSpan(selectionRange.textSpan, scriptInfo),
2838            };
2839            if (selectionRange.parent) {
2840                result.parent = this.mapSelectionRange(selectionRange.parent, scriptInfo);
2841            }
2842            return result;
2843        }
2844
2845        private getScriptInfoFromProjectService(file: string) {
2846            const normalizedFile = toNormalizedPath(file);
2847            const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(normalizedFile);
2848            if (!scriptInfo) {
2849                this.projectService.logErrorForScriptInfoNotFound(normalizedFile);
2850                return Errors.ThrowNoProject();
2851            }
2852            return scriptInfo;
2853        }
2854
2855        private toProtocolCallHierarchyItem(item: CallHierarchyItem): protocol.CallHierarchyItem {
2856            const scriptInfo = this.getScriptInfoFromProjectService(item.file);
2857            return {
2858                name: item.name,
2859                kind: item.kind,
2860                kindModifiers: item.kindModifiers,
2861                file: item.file,
2862                containerName: item.containerName,
2863                span: toProtocolTextSpan(item.span, scriptInfo),
2864                selectionSpan: toProtocolTextSpan(item.selectionSpan, scriptInfo)
2865            };
2866        }
2867
2868        private toProtocolCallHierarchyIncomingCall(incomingCall: CallHierarchyIncomingCall): protocol.CallHierarchyIncomingCall {
2869            const scriptInfo = this.getScriptInfoFromProjectService(incomingCall.from.file);
2870            return {
2871                from: this.toProtocolCallHierarchyItem(incomingCall.from),
2872                fromSpans: incomingCall.fromSpans.map(fromSpan => toProtocolTextSpan(fromSpan, scriptInfo))
2873            };
2874        }
2875
2876        private toProtocolCallHierarchyOutgoingCall(outgoingCall: CallHierarchyOutgoingCall, scriptInfo: ScriptInfo): protocol.CallHierarchyOutgoingCall {
2877            return {
2878                to: this.toProtocolCallHierarchyItem(outgoingCall.to),
2879                fromSpans: outgoingCall.fromSpans.map(fromSpan => toProtocolTextSpan(fromSpan, scriptInfo))
2880            };
2881        }
2882
2883        private prepareCallHierarchy(args: protocol.FileLocationRequestArgs): protocol.CallHierarchyItem | protocol.CallHierarchyItem[] | undefined {
2884            const { file, project } = this.getFileAndProject(args);
2885            const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file);
2886            if (scriptInfo) {
2887                const position = this.getPosition(args, scriptInfo);
2888                const result = project.getLanguageService().prepareCallHierarchy(file, position);
2889                return result && mapOneOrMany(result, item => this.toProtocolCallHierarchyItem(item));
2890            }
2891            return undefined;
2892        }
2893
2894        private provideCallHierarchyIncomingCalls(args: protocol.FileLocationRequestArgs): protocol.CallHierarchyIncomingCall[] {
2895            const { file, project } = this.getFileAndProject(args);
2896            const scriptInfo = this.getScriptInfoFromProjectService(file);
2897            const incomingCalls = project.getLanguageService().provideCallHierarchyIncomingCalls(file, this.getPosition(args, scriptInfo));
2898            return incomingCalls.map(call => this.toProtocolCallHierarchyIncomingCall(call));
2899        }
2900
2901        private provideCallHierarchyOutgoingCalls(args: protocol.FileLocationRequestArgs): protocol.CallHierarchyOutgoingCall[] {
2902            const { file, project } = this.getFileAndProject(args);
2903            const scriptInfo = this.getScriptInfoFromProjectService(file);
2904            const outgoingCalls = project.getLanguageService().provideCallHierarchyOutgoingCalls(file, this.getPosition(args, scriptInfo));
2905            return outgoingCalls.map(call => this.toProtocolCallHierarchyOutgoingCall(call, scriptInfo));
2906        }
2907
2908        getCanonicalFileName(fileName: string) {
2909            const name = this.host.useCaseSensitiveFileNames ? fileName : toFileNameLowerCase(fileName);
2910            return normalizePath(name);
2911        }
2912
2913        exit() { /*overridden*/ }
2914
2915        private notRequired(): HandlerResponse {
2916            return { responseRequired: false };
2917        }
2918
2919        private requiredResponse(response: {} | undefined): HandlerResponse {
2920            return { response, responseRequired: true };
2921        }
2922
2923        private handlers = new Map(getEntries<(request: protocol.Request) => HandlerResponse>({
2924            [CommandNames.Status]: () => {
2925                const response: protocol.StatusResponseBody = { version: ts.version }; // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier
2926                return this.requiredResponse(response);
2927            },
2928            [CommandNames.OpenExternalProject]: (request: protocol.OpenExternalProjectRequest) => {
2929                this.projectService.openExternalProject(request.arguments);
2930                // TODO: GH#20447 report errors
2931                return this.requiredResponse(/*response*/ true);
2932            },
2933            [CommandNames.OpenExternalProjects]: (request: protocol.OpenExternalProjectsRequest) => {
2934                this.projectService.openExternalProjects(request.arguments.projects);
2935                // TODO: GH#20447 report errors
2936                return this.requiredResponse(/*response*/ true);
2937            },
2938            [CommandNames.CloseExternalProject]: (request: protocol.CloseExternalProjectRequest) => {
2939                this.projectService.closeExternalProject(request.arguments.projectFileName);
2940                // TODO: GH#20447 report errors
2941                return this.requiredResponse(/*response*/ true);
2942            },
2943            [CommandNames.SynchronizeProjectList]: (request: protocol.SynchronizeProjectListRequest) => {
2944                const result = this.projectService.synchronizeProjectList(request.arguments.knownProjects, request.arguments.includeProjectReferenceRedirectInfo);
2945                if (!result.some(p => p.projectErrors && p.projectErrors.length !== 0)) {
2946                    return this.requiredResponse(result);
2947                }
2948                const converted = map(result, p => {
2949                    if (!p.projectErrors || p.projectErrors.length === 0) {
2950                        return p;
2951                    }
2952                    return {
2953                        info: p.info,
2954                        changes: p.changes,
2955                        files: p.files,
2956                        projectErrors: this.convertToDiagnosticsWithLinePosition(p.projectErrors, /*scriptInfo*/ undefined)
2957                    };
2958                });
2959                return this.requiredResponse(converted);
2960            },
2961            [CommandNames.UpdateOpen]: (request: protocol.UpdateOpenRequest) => {
2962                this.changeSeq++;
2963                this.projectService.applyChangesInOpenFiles(
2964                    request.arguments.openFiles && mapIterator(arrayIterator(request.arguments.openFiles), file => ({
2965                        fileName: file.file,
2966                        content: file.fileContent,
2967                        scriptKind: file.scriptKindName,
2968                        projectRootPath: file.projectRootPath
2969                    })),
2970                    request.arguments.changedFiles && mapIterator(arrayIterator(request.arguments.changedFiles), file => ({
2971                        fileName: file.fileName,
2972                        changes: mapDefinedIterator(arrayReverseIterator(file.textChanges), change => {
2973                            const scriptInfo = Debug.checkDefined(this.projectService.getScriptInfo(file.fileName));
2974                            const start = scriptInfo.lineOffsetToPosition(change.start.line, change.start.offset);
2975                            const end = scriptInfo.lineOffsetToPosition(change.end.line, change.end.offset);
2976                            return start >= 0 ? { span: { start, length: end - start }, newText: change.newText } : undefined;
2977                        })
2978                    })),
2979                    request.arguments.closedFiles
2980                );
2981                return this.requiredResponse(/*response*/ true);
2982            },
2983            [CommandNames.ApplyChangedToOpenFiles]: (request: protocol.ApplyChangedToOpenFilesRequest) => {
2984                this.changeSeq++;
2985                this.projectService.applyChangesInOpenFiles(
2986                    request.arguments.openFiles && arrayIterator(request.arguments.openFiles),
2987                    request.arguments.changedFiles && mapIterator(arrayIterator(request.arguments.changedFiles), file => ({
2988                        fileName: file.fileName,
2989                        // apply changes in reverse order
2990                        changes: arrayReverseIterator(file.changes)
2991                    })),
2992                    request.arguments.closedFiles
2993                );
2994                // TODO: report errors
2995                return this.requiredResponse(/*response*/ true);
2996            },
2997            [CommandNames.Exit]: () => {
2998                this.exit();
2999                return this.notRequired();
3000            },
3001            [CommandNames.Definition]: (request: protocol.DefinitionRequest) => {
3002                return this.requiredResponse(this.getDefinition(request.arguments, /*simplifiedResult*/ true));
3003            },
3004            [CommandNames.DefinitionFull]: (request: protocol.DefinitionRequest) => {
3005                return this.requiredResponse(this.getDefinition(request.arguments, /*simplifiedResult*/ false));
3006            },
3007            [CommandNames.DefinitionAndBoundSpan]: (request: protocol.DefinitionAndBoundSpanRequest) => {
3008                return this.requiredResponse(this.getDefinitionAndBoundSpan(request.arguments, /*simplifiedResult*/ true));
3009            },
3010            [CommandNames.DefinitionAndBoundSpanFull]: (request: protocol.DefinitionAndBoundSpanRequest) => {
3011                return this.requiredResponse(this.getDefinitionAndBoundSpan(request.arguments, /*simplifiedResult*/ false));
3012            },
3013            [CommandNames.FindSourceDefinition]: (request: protocol.FindSourceDefinitionRequest) => {
3014                return this.requiredResponse(this.findSourceDefinition(request.arguments));
3015            },
3016            [CommandNames.EmitOutput]: (request: protocol.EmitOutputRequest) => {
3017                return this.requiredResponse(this.getEmitOutput(request.arguments));
3018            },
3019            [CommandNames.TypeDefinition]: (request: protocol.FileLocationRequest) => {
3020                return this.requiredResponse(this.getTypeDefinition(request.arguments));
3021            },
3022            [CommandNames.Implementation]: (request: protocol.Request) => {
3023                return this.requiredResponse(this.getImplementation(request.arguments, /*simplifiedResult*/ true));
3024            },
3025            [CommandNames.ImplementationFull]: (request: protocol.Request) => {
3026                return this.requiredResponse(this.getImplementation(request.arguments, /*simplifiedResult*/ false));
3027            },
3028            [CommandNames.References]: (request: protocol.FileLocationRequest) => {
3029                return this.requiredResponse(this.getReferences(request.arguments, /*simplifiedResult*/ true));
3030            },
3031            [CommandNames.ReferencesFull]: (request: protocol.FileLocationRequest) => {
3032                return this.requiredResponse(this.getReferences(request.arguments, /*simplifiedResult*/ false));
3033            },
3034            [CommandNames.Rename]: (request: protocol.RenameRequest) => {
3035                return this.requiredResponse(this.getRenameLocations(request.arguments, /*simplifiedResult*/ true));
3036            },
3037            [CommandNames.RenameLocationsFull]: (request: protocol.RenameFullRequest) => {
3038                return this.requiredResponse(this.getRenameLocations(request.arguments, /*simplifiedResult*/ false));
3039            },
3040            [CommandNames.RenameInfoFull]: (request: protocol.FileLocationRequest) => {
3041                return this.requiredResponse(this.getRenameInfo(request.arguments));
3042            },
3043            [CommandNames.Open]: (request: protocol.OpenRequest) => {
3044                this.openClientFile(
3045                    toNormalizedPath(request.arguments.file),
3046                    request.arguments.fileContent,
3047                    convertScriptKindName(request.arguments.scriptKindName!), // TODO: GH#18217
3048                    request.arguments.projectRootPath ? toNormalizedPath(request.arguments.projectRootPath) : undefined);
3049                return this.notRequired();
3050            },
3051            [CommandNames.Quickinfo]: (request: protocol.QuickInfoRequest) => {
3052                return this.requiredResponse(this.getQuickInfoWorker(request.arguments, /*simplifiedResult*/ true));
3053            },
3054            [CommandNames.QuickinfoFull]: (request: protocol.QuickInfoRequest) => {
3055                return this.requiredResponse(this.getQuickInfoWorker(request.arguments, /*simplifiedResult*/ false));
3056            },
3057            [CommandNames.GetOutliningSpans]: (request: protocol.FileRequest) => {
3058                return this.requiredResponse(this.getOutliningSpans(request.arguments, /*simplifiedResult*/ true));
3059            },
3060            [CommandNames.GetOutliningSpansFull]: (request: protocol.FileRequest) => {
3061                return this.requiredResponse(this.getOutliningSpans(request.arguments, /*simplifiedResult*/ false));
3062            },
3063            [CommandNames.TodoComments]: (request: protocol.TodoCommentRequest) => {
3064                return this.requiredResponse(this.getTodoComments(request.arguments));
3065            },
3066            [CommandNames.Indentation]: (request: protocol.IndentationRequest) => {
3067                return this.requiredResponse(this.getIndentation(request.arguments));
3068            },
3069            [CommandNames.NameOrDottedNameSpan]: (request: protocol.FileLocationRequest) => {
3070                return this.requiredResponse(this.getNameOrDottedNameSpan(request.arguments));
3071            },
3072            [CommandNames.BreakpointStatement]: (request: protocol.FileLocationRequest) => {
3073                return this.requiredResponse(this.getBreakpointStatement(request.arguments));
3074            },
3075            [CommandNames.BraceCompletion]: (request: protocol.BraceCompletionRequest) => {
3076                return this.requiredResponse(this.isValidBraceCompletion(request.arguments));
3077            },
3078            [CommandNames.DocCommentTemplate]: (request: protocol.DocCommentTemplateRequest) => {
3079                return this.requiredResponse(this.getDocCommentTemplate(request.arguments));
3080            },
3081            [CommandNames.GetSpanOfEnclosingComment]: (request: protocol.SpanOfEnclosingCommentRequest) => {
3082                return this.requiredResponse(this.getSpanOfEnclosingComment(request.arguments));
3083            },
3084            [CommandNames.FileReferences]: (request: protocol.FileReferencesRequest) => {
3085                return this.requiredResponse(this.getFileReferences(request.arguments, /*simplifiedResult*/ true));
3086            },
3087            [CommandNames.FileReferencesFull]: (request: protocol.FileReferencesRequest) => {
3088                return this.requiredResponse(this.getFileReferences(request.arguments, /*simplifiedResult*/ false));
3089            },
3090            [CommandNames.Format]: (request: protocol.FormatRequest) => {
3091                return this.requiredResponse(this.getFormattingEditsForRange(request.arguments));
3092            },
3093            [CommandNames.Formatonkey]: (request: protocol.FormatOnKeyRequest) => {
3094                return this.requiredResponse(this.getFormattingEditsAfterKeystroke(request.arguments));
3095            },
3096            [CommandNames.FormatFull]: (request: protocol.FormatRequest) => {
3097                return this.requiredResponse(this.getFormattingEditsForDocumentFull(request.arguments));
3098            },
3099            [CommandNames.FormatonkeyFull]: (request: protocol.FormatOnKeyRequest) => {
3100                return this.requiredResponse(this.getFormattingEditsAfterKeystrokeFull(request.arguments));
3101            },
3102            [CommandNames.FormatRangeFull]: (request: protocol.FormatRequest) => {
3103                return this.requiredResponse(this.getFormattingEditsForRangeFull(request.arguments));
3104            },
3105            [CommandNames.CompletionInfo]: (request: protocol.CompletionsRequest) => {
3106                return this.requiredResponse(this.getCompletions(request.arguments, CommandNames.CompletionInfo));
3107            },
3108            [CommandNames.Completions]: (request: protocol.CompletionsRequest) => {
3109                return this.requiredResponse(this.getCompletions(request.arguments, CommandNames.Completions));
3110            },
3111            [CommandNames.CompletionsFull]: (request: protocol.CompletionsRequest) => {
3112                return this.requiredResponse(this.getCompletions(request.arguments, CommandNames.CompletionsFull));
3113            },
3114            [CommandNames.CompletionDetails]: (request: protocol.CompletionDetailsRequest) => {
3115                return this.requiredResponse(this.getCompletionEntryDetails(request.arguments, /*fullResult*/ false));
3116            },
3117            [CommandNames.CompletionDetailsFull]: (request: protocol.CompletionDetailsRequest) => {
3118                return this.requiredResponse(this.getCompletionEntryDetails(request.arguments, /*fullResult*/ true));
3119            },
3120            [CommandNames.CompileOnSaveAffectedFileList]: (request: protocol.CompileOnSaveAffectedFileListRequest) => {
3121                return this.requiredResponse(this.getCompileOnSaveAffectedFileList(request.arguments));
3122            },
3123            [CommandNames.CompileOnSaveEmitFile]: (request: protocol.CompileOnSaveEmitFileRequest) => {
3124                return this.requiredResponse(this.emitFile(request.arguments));
3125            },
3126            [CommandNames.SignatureHelp]: (request: protocol.SignatureHelpRequest) => {
3127                return this.requiredResponse(this.getSignatureHelpItems(request.arguments, /*simplifiedResult*/ true));
3128            },
3129            [CommandNames.SignatureHelpFull]: (request: protocol.SignatureHelpRequest) => {
3130                return this.requiredResponse(this.getSignatureHelpItems(request.arguments, /*simplifiedResult*/ false));
3131            },
3132            [CommandNames.CompilerOptionsDiagnosticsFull]: (request: protocol.CompilerOptionsDiagnosticsRequest) => {
3133                return this.requiredResponse(this.getCompilerOptionsDiagnostics(request.arguments));
3134            },
3135            [CommandNames.EncodedSyntacticClassificationsFull]: (request: protocol.EncodedSyntacticClassificationsRequest) => {
3136                return this.requiredResponse(this.getEncodedSyntacticClassifications(request.arguments));
3137            },
3138            [CommandNames.EncodedSemanticClassificationsFull]: (request: protocol.EncodedSemanticClassificationsRequest) => {
3139                return this.requiredResponse(this.getEncodedSemanticClassifications(request.arguments));
3140            },
3141            [CommandNames.Cleanup]: () => {
3142                this.cleanup();
3143                return this.requiredResponse(/*response*/ true);
3144            },
3145            [CommandNames.SemanticDiagnosticsSync]: (request: protocol.SemanticDiagnosticsSyncRequest) => {
3146                return this.requiredResponse(this.getSemanticDiagnosticsSync(request.arguments));
3147            },
3148            [CommandNames.SyntacticDiagnosticsSync]: (request: protocol.SyntacticDiagnosticsSyncRequest) => {
3149                return this.requiredResponse(this.getSyntacticDiagnosticsSync(request.arguments));
3150            },
3151            [CommandNames.SuggestionDiagnosticsSync]: (request: protocol.SuggestionDiagnosticsSyncRequest) => {
3152                return this.requiredResponse(this.getSuggestionDiagnosticsSync(request.arguments));
3153            },
3154            [CommandNames.Geterr]: (request: protocol.GeterrRequest) => {
3155                this.errorCheck.startNew(next => this.getDiagnostics(next, request.arguments.delay, request.arguments.files));
3156                return this.notRequired();
3157            },
3158            [CommandNames.GeterrForProject]: (request: protocol.GeterrForProjectRequest) => {
3159                this.errorCheck.startNew(next => this.getDiagnosticsForProject(next, request.arguments.delay, request.arguments.file));
3160                return this.notRequired();
3161            },
3162            [CommandNames.Change]: (request: protocol.ChangeRequest) => {
3163                this.change(request.arguments);
3164                return this.notRequired();
3165            },
3166            [CommandNames.Configure]: (request: protocol.ConfigureRequest) => {
3167                this.projectService.setHostConfiguration(request.arguments);
3168                this.doOutput(/*info*/ undefined, CommandNames.Configure, request.seq, /*success*/ true);
3169                return this.notRequired();
3170            },
3171            [CommandNames.Reload]: (request: protocol.ReloadRequest) => {
3172                this.reload(request.arguments, request.seq);
3173                return this.requiredResponse({ reloadFinished: true });
3174            },
3175            [CommandNames.Saveto]: (request: protocol.Request) => {
3176                const savetoArgs = request.arguments as protocol.SavetoRequestArgs;
3177                this.saveToTmp(savetoArgs.file, savetoArgs.tmpfile);
3178                return this.notRequired();
3179            },
3180            [CommandNames.Close]: (request: protocol.Request) => {
3181                const closeArgs = request.arguments as protocol.FileRequestArgs;
3182                this.closeClientFile(closeArgs.file);
3183                return this.notRequired();
3184            },
3185            [CommandNames.Navto]: (request: protocol.NavtoRequest) => {
3186                return this.requiredResponse(this.getNavigateToItems(request.arguments, /*simplifiedResult*/ true));
3187            },
3188            [CommandNames.NavtoFull]: (request: protocol.NavtoRequest) => {
3189                return this.requiredResponse(this.getNavigateToItems(request.arguments, /*simplifiedResult*/ false));
3190            },
3191            [CommandNames.Brace]: (request: protocol.FileLocationRequest) => {
3192                return this.requiredResponse(this.getBraceMatching(request.arguments, /*simplifiedResult*/ true));
3193            },
3194            [CommandNames.BraceFull]: (request: protocol.FileLocationRequest) => {
3195                return this.requiredResponse(this.getBraceMatching(request.arguments, /*simplifiedResult*/ false));
3196            },
3197            [CommandNames.NavBar]: (request: protocol.FileRequest) => {
3198                return this.requiredResponse(this.getNavigationBarItems(request.arguments, /*simplifiedResult*/ true));
3199            },
3200            [CommandNames.NavBarFull]: (request: protocol.FileRequest) => {
3201                return this.requiredResponse(this.getNavigationBarItems(request.arguments, /*simplifiedResult*/ false));
3202            },
3203            [CommandNames.NavTree]: (request: protocol.FileRequest) => {
3204                return this.requiredResponse(this.getNavigationTree(request.arguments, /*simplifiedResult*/ true));
3205            },
3206            [CommandNames.NavTreeFull]: (request: protocol.FileRequest) => {
3207                return this.requiredResponse(this.getNavigationTree(request.arguments, /*simplifiedResult*/ false));
3208            },
3209            [CommandNames.Occurrences]: (request: protocol.FileLocationRequest) => {
3210                return this.requiredResponse(this.getOccurrences(request.arguments));
3211            },
3212            [CommandNames.DocumentHighlights]: (request: protocol.DocumentHighlightsRequest) => {
3213                return this.requiredResponse(this.getDocumentHighlights(request.arguments, /*simplifiedResult*/ true));
3214            },
3215            [CommandNames.DocumentHighlightsFull]: (request: protocol.DocumentHighlightsRequest) => {
3216                return this.requiredResponse(this.getDocumentHighlights(request.arguments, /*simplifiedResult*/ false));
3217            },
3218            [CommandNames.CompilerOptionsForInferredProjects]: (request: protocol.SetCompilerOptionsForInferredProjectsRequest) => {
3219                this.setCompilerOptionsForInferredProjects(request.arguments);
3220                return this.requiredResponse(/*response*/ true);
3221            },
3222            [CommandNames.ProjectInfo]: (request: protocol.ProjectInfoRequest) => {
3223                return this.requiredResponse(this.getProjectInfo(request.arguments));
3224            },
3225            [CommandNames.ReloadProjects]: () => {
3226                this.projectService.reloadProjects();
3227                return this.notRequired();
3228            },
3229            [CommandNames.JsxClosingTag]: (request: protocol.JsxClosingTagRequest) => {
3230                return this.requiredResponse(this.getJsxClosingTag(request.arguments));
3231            },
3232            [CommandNames.GetCodeFixes]: (request: protocol.CodeFixRequest) => {
3233                return this.requiredResponse(this.getCodeFixes(request.arguments, /*simplifiedResult*/ true));
3234            },
3235            [CommandNames.GetCodeFixesFull]: (request: protocol.CodeFixRequest) => {
3236                return this.requiredResponse(this.getCodeFixes(request.arguments, /*simplifiedResult*/ false));
3237            },
3238            [CommandNames.GetCombinedCodeFix]: (request: protocol.GetCombinedCodeFixRequest) => {
3239                return this.requiredResponse(this.getCombinedCodeFix(request.arguments, /*simplifiedResult*/ true));
3240            },
3241            [CommandNames.GetCombinedCodeFixFull]: (request: protocol.GetCombinedCodeFixRequest) => {
3242                return this.requiredResponse(this.getCombinedCodeFix(request.arguments, /*simplifiedResult*/ false));
3243            },
3244            [CommandNames.ApplyCodeActionCommand]: (request: protocol.ApplyCodeActionCommandRequest) => {
3245                return this.requiredResponse(this.applyCodeActionCommand(request.arguments));
3246            },
3247            [CommandNames.GetSupportedCodeFixes]: () => {
3248                return this.requiredResponse(this.getSupportedCodeFixes());
3249            },
3250            [CommandNames.GetApplicableRefactors]: (request: protocol.GetApplicableRefactorsRequest) => {
3251                return this.requiredResponse(this.getApplicableRefactors(request.arguments));
3252            },
3253            [CommandNames.GetEditsForRefactor]: (request: protocol.GetEditsForRefactorRequest) => {
3254                return this.requiredResponse(this.getEditsForRefactor(request.arguments, /*simplifiedResult*/ true));
3255            },
3256            [CommandNames.GetEditsForRefactorFull]: (request: protocol.GetEditsForRefactorRequest) => {
3257                return this.requiredResponse(this.getEditsForRefactor(request.arguments, /*simplifiedResult*/ false));
3258            },
3259            [CommandNames.OrganizeImports]: (request: protocol.OrganizeImportsRequest) => {
3260                return this.requiredResponse(this.organizeImports(request.arguments, /*simplifiedResult*/ true));
3261            },
3262            [CommandNames.OrganizeImportsFull]: (request: protocol.OrganizeImportsRequest) => {
3263                return this.requiredResponse(this.organizeImports(request.arguments, /*simplifiedResult*/ false));
3264            },
3265            [CommandNames.GetEditsForFileRename]: (request: protocol.GetEditsForFileRenameRequest) => {
3266                return this.requiredResponse(this.getEditsForFileRename(request.arguments, /*simplifiedResult*/ true));
3267            },
3268            [CommandNames.GetEditsForFileRenameFull]: (request: protocol.GetEditsForFileRenameRequest) => {
3269                return this.requiredResponse(this.getEditsForFileRename(request.arguments, /*simplifiedResult*/ false));
3270            },
3271            [CommandNames.ConfigurePlugin]: (request: protocol.ConfigurePluginRequest) => {
3272                this.configurePlugin(request.arguments);
3273                this.doOutput(/*info*/ undefined, CommandNames.ConfigurePlugin, request.seq, /*success*/ true);
3274                return this.notRequired();
3275            },
3276            [CommandNames.SelectionRange]: (request: protocol.SelectionRangeRequest) => {
3277                return this.requiredResponse(this.getSmartSelectionRange(request.arguments, /*simplifiedResult*/ true));
3278            },
3279            [CommandNames.SelectionRangeFull]: (request: protocol.SelectionRangeRequest) => {
3280                return this.requiredResponse(this.getSmartSelectionRange(request.arguments, /*simplifiedResult*/ false));
3281            },
3282            [CommandNames.PrepareCallHierarchy]: (request: protocol.PrepareCallHierarchyRequest) => {
3283                return this.requiredResponse(this.prepareCallHierarchy(request.arguments));
3284            },
3285            [CommandNames.ProvideCallHierarchyIncomingCalls]: (request: protocol.ProvideCallHierarchyIncomingCallsRequest) => {
3286                return this.requiredResponse(this.provideCallHierarchyIncomingCalls(request.arguments));
3287            },
3288            [CommandNames.ProvideCallHierarchyOutgoingCalls]: (request: protocol.ProvideCallHierarchyOutgoingCallsRequest) => {
3289                return this.requiredResponse(this.provideCallHierarchyOutgoingCalls(request.arguments));
3290            },
3291            [CommandNames.ToggleLineComment]: (request: protocol.ToggleLineCommentRequest) => {
3292                return this.requiredResponse(this.toggleLineComment(request.arguments, /*simplifiedResult*/ true));
3293            },
3294            [CommandNames.ToggleLineCommentFull]: (request: protocol.ToggleLineCommentRequest) => {
3295                return this.requiredResponse(this.toggleLineComment(request.arguments, /*simplifiedResult*/ false));
3296            },
3297            [CommandNames.ToggleMultilineComment]: (request: protocol.ToggleMultilineCommentRequest) => {
3298                return this.requiredResponse(this.toggleMultilineComment(request.arguments, /*simplifiedResult*/ true));
3299            },
3300            [CommandNames.ToggleMultilineCommentFull]: (request: protocol.ToggleMultilineCommentRequest) => {
3301                return this.requiredResponse(this.toggleMultilineComment(request.arguments, /*simplifiedResult*/ false));
3302            },
3303            [CommandNames.CommentSelection]: (request: protocol.CommentSelectionRequest) => {
3304                return this.requiredResponse(this.commentSelection(request.arguments, /*simplifiedResult*/ true));
3305            },
3306            [CommandNames.CommentSelectionFull]: (request: protocol.CommentSelectionRequest) => {
3307                return this.requiredResponse(this.commentSelection(request.arguments, /*simplifiedResult*/ false));
3308            },
3309            [CommandNames.UncommentSelection]: (request: protocol.UncommentSelectionRequest) => {
3310                return this.requiredResponse(this.uncommentSelection(request.arguments, /*simplifiedResult*/ true));
3311            },
3312            [CommandNames.UncommentSelectionFull]: (request: protocol.UncommentSelectionRequest) => {
3313                return this.requiredResponse(this.uncommentSelection(request.arguments, /*simplifiedResult*/ false));
3314            },
3315            [CommandNames.ProvideInlayHints]: (request: protocol.InlayHintsRequest) => {
3316                return this.requiredResponse(this.provideInlayHints(request.arguments));
3317            }
3318        }));
3319
3320        public addProtocolHandler(command: string, handler: (request: protocol.Request) => HandlerResponse) {
3321            if (this.handlers.has(command)) {
3322                throw new Error(`Protocol handler already exists for command "${command}"`);
3323            }
3324            this.handlers.set(command, handler);
3325        }
3326
3327        private setCurrentRequest(requestId: number): void {
3328            Debug.assert(this.currentRequestId === undefined);
3329            this.currentRequestId = requestId;
3330            this.cancellationToken.setRequest(requestId);
3331        }
3332
3333        private resetCurrentRequest(requestId: number): void {
3334            Debug.assert(this.currentRequestId === requestId);
3335            this.currentRequestId = undefined!; // TODO: GH#18217
3336            this.cancellationToken.resetRequest(requestId);
3337        }
3338
3339        public executeWithRequestId<T>(requestId: number, f: () => T) {
3340            try {
3341                this.setCurrentRequest(requestId);
3342                return f();
3343            }
3344            finally {
3345                this.resetCurrentRequest(requestId);
3346            }
3347        }
3348
3349        public executeCommand(request: protocol.Request): HandlerResponse {
3350            const handler = this.handlers.get(request.command);
3351            if (handler) {
3352                const response = this.executeWithRequestId(request.seq, () => handler(request));
3353                this.projectService.enableRequestedPlugins();
3354                return response;
3355            }
3356            else {
3357                this.logger.msg(`Unrecognized JSON command:${stringifyIndented(request)}`, Msg.Err);
3358                this.doOutput(/*info*/ undefined, CommandNames.Unknown, request.seq, /*success*/ false, `Unrecognized JSON command: ${request.command}`);
3359                return { responseRequired: false };
3360            }
3361        }
3362
3363        public onMessage(message: TMessage) {
3364            this.gcTimer.scheduleCollect();
3365
3366            this.performanceData = undefined;
3367
3368            let start: number[] | undefined;
3369            if (this.logger.hasLevel(LogLevel.requestTime)) {
3370                start = this.hrtime();
3371                if (this.logger.hasLevel(LogLevel.verbose)) {
3372                    this.logger.info(`request:${indent(this.toStringMessage(message))}`);
3373                }
3374            }
3375
3376            let request: protocol.Request | undefined;
3377            let relevantFile: protocol.FileRequestArgs | undefined;
3378            try {
3379                request = this.parseMessage(message);
3380                relevantFile = request.arguments && (request as protocol.FileRequest).arguments.file ? (request as protocol.FileRequest).arguments : undefined;
3381
3382                tracing?.instant(tracing.Phase.Session, "request", { seq: request.seq, command: request.command });
3383                perfLogger.logStartCommand("" + request.command, this.toStringMessage(message).substring(0, 100));
3384
3385                tracing?.push(tracing.Phase.Session, "executeCommand", { seq: request.seq, command: request.command }, /*separateBeginAndEnd*/ true);
3386                const { response, responseRequired } = this.executeCommand(request);
3387                tracing?.pop();
3388
3389                if (this.logger.hasLevel(LogLevel.requestTime)) {
3390                    const elapsedTime = hrTimeToMilliseconds(this.hrtime(start)).toFixed(4);
3391                    if (responseRequired) {
3392                        this.logger.perftrc(`${request.seq}::${request.command}: elapsed time (in milliseconds) ${elapsedTime}`);
3393                    }
3394                    else {
3395                        this.logger.perftrc(`${request.seq}::${request.command}: async elapsed time (in milliseconds) ${elapsedTime}`);
3396                    }
3397                }
3398
3399                // Note: Log before writing the response, else the editor can complete its activity before the server does
3400                perfLogger.logStopCommand("" + request.command, "Success");
3401                tracing?.instant(tracing.Phase.Session, "response", { seq: request.seq, command: request.command, success: !!response });
3402                if (response) {
3403                    this.doOutput(response, request.command, request.seq, /*success*/ true);
3404                }
3405                else if (responseRequired) {
3406                    this.doOutput(/*info*/ undefined, request.command, request.seq, /*success*/ false, "No content available.");
3407                }
3408            }
3409            catch (err) {
3410                // Cancellation or an error may have left incomplete events on the tracing stack.
3411                tracing?.popAll();
3412
3413                if (err instanceof OperationCanceledException) {
3414                    // Handle cancellation exceptions
3415                    perfLogger.logStopCommand("" + (request && request.command), "Canceled: " + err);
3416                    tracing?.instant(tracing.Phase.Session, "commandCanceled", { seq: request?.seq, command: request?.command });
3417                    this.doOutput({ canceled: true }, request!.command, request!.seq, /*success*/ true);
3418                    return;
3419                }
3420
3421                this.logErrorWorker(err, this.toStringMessage(message), relevantFile);
3422                perfLogger.logStopCommand("" + (request && request.command), "Error: " + err);
3423                tracing?.instant(tracing.Phase.Session, "commandError", { seq: request?.seq, command: request?.command, message: (err as Error).message });
3424
3425                this.doOutput(
3426                    /*info*/ undefined,
3427                    request ? request.command : CommandNames.Unknown,
3428                    request ? request.seq : 0,
3429                    /*success*/ false,
3430                    "Error processing request. " + (err as StackTraceError).message + "\n" + (err as StackTraceError).stack);
3431            }
3432        }
3433
3434        protected parseMessage(message: TMessage): protocol.Request {
3435            return JSON.parse(message as any as string) as protocol.Request;
3436        }
3437
3438        protected toStringMessage(message: TMessage): string {
3439            return message as any as string;
3440        }
3441
3442        private getFormatOptions(file: NormalizedPath): FormatCodeSettings {
3443            return this.projectService.getFormatCodeOptions(file);
3444        }
3445
3446        private getPreferences(file: NormalizedPath): protocol.UserPreferences {
3447            return this.projectService.getPreferences(file);
3448        }
3449
3450        private getHostFormatOptions(): FormatCodeSettings {
3451            return this.projectService.getHostFormatCodeOptions();
3452        }
3453
3454        private getHostPreferences(): protocol.UserPreferences {
3455            return this.projectService.getHostPreferences();
3456        }
3457    }
3458
3459    interface FileAndProject {
3460        readonly file: NormalizedPath;
3461        readonly project: Project;
3462    }
3463
3464    function toProtocolTextSpan(textSpan: TextSpan, scriptInfo: ScriptInfo): protocol.TextSpan {
3465        return {
3466            start: scriptInfo.positionToLineOffset(textSpan.start),
3467            end: scriptInfo.positionToLineOffset(textSpanEnd(textSpan))
3468        };
3469    }
3470
3471    function toProtocolTextSpanWithContext(span: TextSpan, contextSpan: TextSpan | undefined, scriptInfo: ScriptInfo): protocol.TextSpanWithContext {
3472        const textSpan = toProtocolTextSpan(span, scriptInfo);
3473        const contextTextSpan = contextSpan && toProtocolTextSpan(contextSpan, scriptInfo);
3474        return contextTextSpan ?
3475            { ...textSpan, contextStart: contextTextSpan.start, contextEnd: contextTextSpan.end } :
3476            textSpan;
3477    }
3478
3479    function convertTextChangeToCodeEdit(change: TextChange, scriptInfo: ScriptInfoOrConfig): protocol.CodeEdit {
3480        return { start: positionToLineOffset(scriptInfo, change.span.start), end: positionToLineOffset(scriptInfo, textSpanEnd(change.span)), newText: change.newText };
3481    }
3482
3483    function positionToLineOffset(info: ScriptInfoOrConfig, position: number): protocol.Location {
3484        return isConfigFile(info) ? locationFromLineAndCharacter(info.getLineAndCharacterOfPosition(position)) : info.positionToLineOffset(position);
3485    }
3486
3487    function locationFromLineAndCharacter(lc: LineAndCharacter): protocol.Location {
3488        return { line: lc.line + 1, offset: lc.character + 1 };
3489    }
3490
3491    function convertNewFileTextChangeToCodeEdit(textChanges: FileTextChanges): protocol.FileCodeEdits {
3492        Debug.assert(textChanges.textChanges.length === 1);
3493        const change = first(textChanges.textChanges);
3494        Debug.assert(change.span.start === 0 && change.span.length === 0);
3495        return { fileName: textChanges.fileName, textChanges: [{ start: { line: 0, offset: 0 }, end: { line: 0, offset: 0 }, newText: change.newText }] };
3496    }
3497
3498    export interface HandlerResponse {
3499        response?: {};
3500        responseRequired?: boolean;
3501    }
3502
3503    /* @internal */ // Exported only for tests
3504    export function getLocationInNewDocument(oldText: string, renameFilename: string, renameLocation: number, edits: readonly FileTextChanges[]): protocol.Location {
3505        const newText = applyEdits(oldText, renameFilename, edits);
3506        const { line, character } = computeLineAndCharacterOfPosition(computeLineStarts(newText), renameLocation);
3507        return { line: line + 1, offset: character + 1 };
3508    }
3509
3510    function applyEdits(text: string, textFilename: string, edits: readonly FileTextChanges[]): string {
3511        for (const { fileName, textChanges } of edits) {
3512            if (fileName !== textFilename) {
3513                continue;
3514            }
3515
3516            for (let i = textChanges.length - 1; i >= 0; i--) {
3517                const { newText, span: { start, length } } = textChanges[i];
3518                text = text.slice(0, start) + newText + text.slice(start + length);
3519            }
3520        }
3521
3522        return text;
3523    }
3524
3525    function referenceEntryToReferencesResponseItem(projectService: ProjectService, { fileName, textSpan, contextSpan, isWriteAccess, isDefinition }: ReferencedSymbolEntry, { disableLineTextInReferences }: protocol.UserPreferences): protocol.ReferencesResponseItem {
3526        const scriptInfo = Debug.checkDefined(projectService.getScriptInfo(fileName));
3527        const span = toProtocolTextSpanWithContext(textSpan, contextSpan, scriptInfo);
3528        const lineText = disableLineTextInReferences ? undefined : getLineText(scriptInfo, span);
3529        return {
3530            file: fileName,
3531            ...span,
3532            lineText,
3533            isWriteAccess,
3534            isDefinition
3535        };
3536    }
3537
3538    function getLineText(scriptInfo: ScriptInfo, span: protocol.TextSpanWithContext) {
3539        const lineSpan = scriptInfo.lineToTextSpan(span.start.line - 1);
3540        return scriptInfo.getSnapshot().getText(lineSpan.start, textSpanEnd(lineSpan)).replace(/\r|\n/g, "");
3541    }
3542
3543    function isCompletionEntryData(data: any): data is CompletionEntryData {
3544        return data === undefined || data && typeof data === "object"
3545            && typeof data.exportName === "string"
3546            && (data.fileName === undefined || typeof data.fileName === "string")
3547            && (data.ambientModuleName === undefined || typeof data.ambientModuleName === "string"
3548            && (data.isPackageJsonImport === undefined || typeof data.isPackageJsonImport === "boolean"));
3549    }
3550}
3551