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