• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import {
2    Classifications, Classifier, clear, CompilerOptions, CompletionEntryData, createClassifier, createDocumentRegistry,
3    createGetCanonicalFileName, createLanguageService, createTextChangeRange, createTextSpan, Diagnostic,
4    diagnosticCategoryName, DocCommentTemplateOptions, DocumentRegistry, EditorOptions, EmitOutput, emptyOptions,
5    EndOfLineState, ESMap, Extension, extensionFromPath, FileReference, filter, flattenDiagnosticMessageText,
6    FormatCodeOptions, FormatCodeSettings, getAutomaticTypeDirectiveNames, GetCompletionsAtPositionOptions,
7    getDefaultCompilerOptions, getDirectoryPath, getFileMatcherPatterns, getNewLineOrDefaultFromHost, getProperty,
8    getSnapshotText, HostCancellationToken, IScriptSnapshot, isString, JsTyping, LanguageService, LanguageServiceHost,
9    map, MapLike, ModuleResolutionHost, normalizeSlashes, OperationCanceledException, ParseConfigHost,
10    parseJsonSourceFileConfigFileContent, parseJsonText, preProcessFile, ReadonlyESMap, ResolvedModuleFull,
11    ResolvedTypeReferenceDirective, resolveModuleName, resolveTypeReferenceDirective, ScriptKind,
12    SemanticClassificationFormat, servicesVersion, SignatureHelpItemsOptions, TextChangeRange, TextRange, TextSpan,
13    ThrottledCancellationToken, timestamp, toFileNameLowerCase, toPath, TypeAcquisition, UserPreferences,
14} from "./_namespaces/ts";
15
16//
17// Copyright (c) Microsoft Corporation.  All rights reserved.
18//
19// Licensed under the Apache License, Version 2.0 (the "License");
20// you may not use this file except in compliance with the License.
21// You may obtain a copy of the License at
22//   http://www.apache.org/licenses/LICENSE-2.0
23//
24// Unless required by applicable law or agreed to in writing, software
25// distributed under the License is distributed on an "AS IS" BASIS,
26// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
27// See the License for the specific language governing permissions and
28// limitations under the License.
29//
30
31/** @internal */
32let debugObjectHost: { CollectGarbage(): void } = (function (this: any) { // eslint-disable-line prefer-const
33    return this;
34})();
35
36// We need to use 'null' to interface with the managed side.
37/* eslint-disable local/no-in-operator */
38
39interface DiscoverTypingsInfo {
40    fileNames: string[];                            // The file names that belong to the same project.
41    projectRootPath: string;                        // The path to the project root directory
42    safeListPath: string;                           // The path used to retrieve the safe list
43    packageNameToTypingLocation: ESMap<string, JsTyping.CachedTyping>;       // The map of package names to their cached typing locations and installed versions
44    typeAcquisition: TypeAcquisition;               // Used to customize the type acquisition process
45    compilerOptions: CompilerOptions;               // Used as a source for typing inference
46    unresolvedImports: readonly string[];       // List of unresolved module ids from imports
47    typesRegistry: ReadonlyESMap<string, MapLike<string>>;    // The map of available typings in npm to maps of TS versions to their latest supported versions
48}
49
50/** @internal */
51export interface ScriptSnapshotShim {
52    /** Gets a portion of the script snapshot specified by [start, end). */
53    getText(start: number, end: number): string;
54
55    /** Gets the length of this script snapshot. */
56    getLength(): number;
57
58    /**
59     * Returns a JSON-encoded value of the type:
60     *   { span: { start: number; length: number }; newLength: number }
61     *
62     * Or undefined value if there was no change.
63     */
64    getChangeRange(oldSnapshot: ScriptSnapshotShim): string | undefined;
65
66    /** Releases all resources held by this script snapshot */
67    dispose?(): void;
68}
69
70/** @internal */
71export interface Logger {
72    log(s: string): void;
73    trace(s: string): void;
74    error(s: string): void;
75}
76
77/**
78 * Public interface of the host of a language service shim instance.
79 *
80 * @internal
81 */
82export interface LanguageServiceShimHost extends Logger {
83    getCompilationSettings(): string;
84
85    /** Returns a JSON-encoded value of the type: string[] */
86    getScriptFileNames(): string;
87    getScriptKind?(fileName: string): ScriptKind;
88    getScriptVersion(fileName: string): string;
89    getScriptSnapshot(fileName: string): ScriptSnapshotShim;
90    getLocalizedDiagnosticMessages(): string;
91    getCancellationToken(): HostCancellationToken;
92    getCurrentDirectory(): string;
93    getDirectories(path: string): string;
94    getDefaultLibFileName(options: string): string;
95    getNewLine?(): string;
96    getProjectVersion?(): string;
97    useCaseSensitiveFileNames?(): boolean;
98
99    getTypeRootsVersion?(): number;
100    readDirectory(rootDir: string, extension: string, basePaths?: string, excludeEx?: string, includeFileEx?: string, includeDirEx?: string, depth?: number): string;
101    readFile(path: string, encoding?: string): string | undefined;
102    fileExists(path: string): boolean;
103
104    getModuleResolutionsForFile?(fileName: string): string;
105    getTypeReferenceDirectiveResolutionsForFile?(fileName: string): string;
106    directoryExists(directoryName: string): boolean;
107}
108
109/**
110 * Public interface of the core-services host instance used in managed side
111 *
112 * @internal
113 */
114export interface CoreServicesShimHost extends Logger {
115    directoryExists(directoryName: string): boolean;
116    fileExists(fileName: string): boolean;
117    getCurrentDirectory(): string;
118    getDirectories(path: string): string;
119
120    /**
121     * Returns a JSON-encoded value of the type: string[]
122     *
123     * @param exclude A JSON encoded string[] containing the paths to exclude
124     *  when enumerating the directory.
125     */
126    readDirectory(rootDir: string, extension: string, basePaths?: string, excludeEx?: string, includeFileEx?: string, includeDirEx?: string, depth?: number): string;
127
128    /**
129     * Read arbitrary text files on disk, i.e. when resolution procedure needs the content of 'package.json' to determine location of bundled typings for node modules
130     */
131    readFile(fileName: string): string | undefined;
132    realpath?(path: string): string;
133    trace(s: string): void;
134    useCaseSensitiveFileNames?(): boolean;
135}
136
137///
138/// Pre-processing
139///
140// Note: This is being using by the host (VS) and is marshaled back and forth.
141// When changing this make sure the changes are reflected in the managed side as well
142/** @internal */
143export interface ShimsFileReference {
144    path: string;
145    position: number;
146    length: number;
147}
148
149/**
150 * Public interface of a language service instance shim.
151 *
152 * @internal
153 */
154export interface ShimFactory {
155    registerShim(shim: Shim): void;
156    unregisterShim(shim: Shim): void;
157}
158
159/** @internal */
160export interface Shim {
161    dispose(_dummy: {}): void;
162}
163
164/** @internal */
165export interface LanguageServiceShim extends Shim {
166    languageService: LanguageService;
167
168    dispose(_dummy: {}): void;
169
170    refresh(throwOnError: boolean): void;
171
172    cleanupSemanticCache(): void;
173
174    getSyntacticDiagnostics(fileName: string): string;
175    getSemanticDiagnostics(fileName: string): string;
176    getSuggestionDiagnostics(fileName: string): string;
177    getCompilerOptionsDiagnostics(): string;
178
179    getSyntacticClassifications(fileName: string, start: number, length: number): string;
180    getSemanticClassifications(fileName: string, start: number, length: number, format?: SemanticClassificationFormat): string;
181    getEncodedSyntacticClassifications(fileName: string, start: number, length: number): string;
182    getEncodedSemanticClassifications(fileName: string, start: number, length: number, format?: SemanticClassificationFormat): string;
183
184    getCompletionsAtPosition(fileName: string, position: number, preferences: UserPreferences | undefined, formattingSettings: FormatCodeSettings | undefined): string;
185    getCompletionEntryDetails(fileName: string, position: number, entryName: string, formatOptions: string/*Services.FormatCodeOptions*/ | undefined, source: string | undefined, preferences: UserPreferences | undefined, data: CompletionEntryData | undefined): string;
186
187    getQuickInfoAtPosition(fileName: string, position: number): string;
188
189    getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): string;
190    getBreakpointStatementAtPosition(fileName: string, position: number): string;
191
192    getSignatureHelpItems(fileName: string, position: number, options: SignatureHelpItemsOptions | undefined): string;
193
194    /**
195     * Returns a JSON-encoded value of the type:
196     * { canRename: boolean, localizedErrorMessage: string, displayName: string, fullDisplayName: string, kind: string, kindModifiers: string, triggerSpan: { start; length } }
197     */
198    getRenameInfo(fileName: string, position: number, preferences: UserPreferences): string;
199    getSmartSelectionRange(fileName: string, position: number): string;
200
201    /**
202     * Returns a JSON-encoded value of the type:
203     * { fileName: string, textSpan: { start: number, length: number } }[]
204     */
205    findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): string;
206
207    /**
208     * Returns a JSON-encoded value of the type:
209     * { fileName: string; textSpan: { start: number; length: number}; kind: string; name: string; containerKind: string; containerName: string }
210     *
211     * Or undefined value if no definition can be found.
212     */
213    getDefinitionAtPosition(fileName: string, position: number): string;
214
215    getDefinitionAndBoundSpan(fileName: string, position: number): string;
216
217    /**
218     * Returns a JSON-encoded value of the type:
219     * { fileName: string; textSpan: { start: number; length: number}; kind: string; name: string; containerKind: string; containerName: string }
220     *
221     * Or undefined value if no definition can be found.
222     */
223    getTypeDefinitionAtPosition(fileName: string, position: number): string;
224
225    /**
226     * Returns a JSON-encoded value of the type:
227     * { fileName: string; textSpan: { start: number; length: number}; }[]
228     */
229    getImplementationAtPosition(fileName: string, position: number): string;
230
231    /**
232     * Returns a JSON-encoded value of the type:
233     * { fileName: string; textSpan: { start: number; length: number}; isWriteAccess: boolean, isDefinition?: boolean }[]
234     */
235    getReferencesAtPosition(fileName: string, position: number): string;
236
237    /**
238     * Returns a JSON-encoded value of the type:
239     * { definition: <encoded>; references: <encoded>[] }[]
240     */
241    findReferences(fileName: string, position: number): string;
242
243    /**
244     * Returns a JSON-encoded value of the type:
245     * { fileName: string; textSpan: { start: number; length: number}; isWriteAccess: boolean, isDefinition?: boolean }[]
246     */
247    getFileReferences(fileName: string): string;
248
249    /**
250     * @deprecated
251     * Returns a JSON-encoded value of the type:
252     * { fileName: string; textSpan: { start: number; length: number}; isWriteAccess: boolean }[]
253     */
254    getOccurrencesAtPosition(fileName: string, position: number): string;
255
256    /**
257     * Returns a JSON-encoded value of the type:
258     * { fileName: string; highlights: { start: number; length: number }[] }[]
259     *
260     * @param fileToSearch A JSON encoded string[] containing the file names that should be
261     *  considered when searching.
262     */
263    getDocumentHighlights(fileName: string, position: number, filesToSearch: string): string;
264
265    /**
266     * Returns a JSON-encoded value of the type:
267     * { name: string; kind: string; kindModifiers: string; containerName: string; containerKind: string; matchKind: string; fileName: string; textSpan: { start: number; length: number}; } [] = [];
268     */
269    getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string): string;
270
271    /**
272     * Returns a JSON-encoded value of the type:
273     * { text: string; kind: string; kindModifiers: string; bolded: boolean; grayed: boolean; indent: number; spans: { start: number; length: number; }[]; childItems: <recursive use of this type>[] } [] = [];
274     */
275    getNavigationBarItems(fileName: string): string;
276
277    /** Returns a JSON-encoded value of the type ts.NavigationTree. */
278    getNavigationTree(fileName: string): string;
279
280    /**
281     * Returns a JSON-encoded value of the type:
282     * { textSpan: { start: number, length: number }; hintSpan: { start: number, length: number }; bannerText: string; autoCollapse: boolean } [] = [];
283     */
284    getOutliningSpans(fileName: string): string;
285
286    getTodoComments(fileName: string, todoCommentDescriptors: string): string;
287
288    getBraceMatchingAtPosition(fileName: string, position: number): string;
289    getIndentationAtPosition(fileName: string, position: number, options: string/*Services.EditorOptions*/): string;
290
291    getFormattingEditsForRange(fileName: string, start: number, end: number, options: string/*Services.FormatCodeOptions*/): string;
292    getFormattingEditsForDocument(fileName: string, options: string/*Services.FormatCodeOptions*/): string;
293    getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: string/*Services.FormatCodeOptions*/): string;
294
295    /**
296     * Returns JSON-encoded value of the type TextInsertion.
297     */
298    getDocCommentTemplateAtPosition(fileName: string, position: number, options?: DocCommentTemplateOptions): string;
299
300    /**
301     * Returns JSON-encoded boolean to indicate whether we should support brace location
302     * at the current position.
303     * E.g. we don't want brace completion inside string-literals, comments, etc.
304     */
305    isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): string;
306
307    /**
308     * Returns a JSON-encoded TextSpan | undefined indicating the range of the enclosing comment, if it exists.
309     */
310    getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): string;
311
312    prepareCallHierarchy(fileName: string, position: number): string;
313    provideCallHierarchyIncomingCalls(fileName: string, position: number): string;
314    provideCallHierarchyOutgoingCalls(fileName: string, position: number): string;
315    provideInlayHints(fileName: string, span: TextSpan, preference: UserPreferences | undefined): string;
316    getEmitOutput(fileName: string): string;
317    getEmitOutputObject(fileName: string): EmitOutput;
318
319    toggleLineComment(fileName: string, textChange: TextRange): string;
320    toggleMultilineComment(fileName: string, textChange: TextRange): string;
321    commentSelection(fileName: string, textChange: TextRange): string;
322    uncommentSelection(fileName: string, textChange: TextRange): string;
323}
324
325/** @internal */
326export interface ClassifierShim extends Shim {
327    getEncodedLexicalClassifications(text: string, lexState: EndOfLineState, syntacticClassifierAbsent?: boolean): string;
328    getClassificationsForLine(text: string, lexState: EndOfLineState, syntacticClassifierAbsent?: boolean): string;
329}
330
331/** @internal */
332export interface CoreServicesShim extends Shim {
333    getAutomaticTypeDirectiveNames(compilerOptionsJson: string): string;
334    getPreProcessedFileInfo(fileName: string, sourceText: IScriptSnapshot): string;
335    getTSConfigFileInfo(fileName: string, sourceText: IScriptSnapshot): string;
336    getDefaultCompilationSettings(): string;
337    discoverTypings(discoverTypingsJson: string): string;
338}
339
340function logInternalError(logger: Logger, err: Error) {
341    if (logger) {
342        logger.log("*INTERNAL ERROR* - Exception in typescript services: " + err.message);
343    }
344}
345
346class ScriptSnapshotShimAdapter implements IScriptSnapshot {
347    constructor(private scriptSnapshotShim: ScriptSnapshotShim) {
348    }
349
350    public getText(start: number, end: number): string {
351        return this.scriptSnapshotShim.getText(start, end);
352    }
353
354    public getLength(): number {
355        return this.scriptSnapshotShim.getLength();
356    }
357
358    public getChangeRange(oldSnapshot: IScriptSnapshot): TextChangeRange | undefined {
359        const oldSnapshotShim = oldSnapshot as ScriptSnapshotShimAdapter;
360        const encoded = this.scriptSnapshotShim.getChangeRange(oldSnapshotShim.scriptSnapshotShim);
361        /* eslint-disable no-null/no-null */
362        if (encoded === null) {
363            return null!; // TODO: GH#18217
364        }
365        /* eslint-enable no-null/no-null */
366
367        const decoded: { span: { start: number; length: number; }; newLength: number; } = JSON.parse(encoded!); // TODO: GH#18217
368        return createTextChangeRange(
369            createTextSpan(decoded.span.start, decoded.span.length), decoded.newLength);
370    }
371
372    public dispose(): void {
373        // if scriptSnapshotShim is a COM object then property check becomes method call with no arguments
374        // 'in' does not have this effect
375        if ("dispose" in this.scriptSnapshotShim) {
376            this.scriptSnapshotShim.dispose!(); // TODO: GH#18217 Can we just use `if (this.scriptSnapshotShim.dispose)`?
377        }
378    }
379}
380
381/** @internal */
382export class LanguageServiceShimHostAdapter implements LanguageServiceHost {
383    private loggingEnabled = false;
384    private tracingEnabled = false;
385
386    public resolveModuleNames: ((moduleName: string[], containingFile: string) => (ResolvedModuleFull | undefined)[]) | undefined;
387    public resolveTypeReferenceDirectives: ((typeDirectiveNames: string[] | readonly FileReference[], containingFile: string) => (ResolvedTypeReferenceDirective | undefined)[]) | undefined;
388    public directoryExists: ((directoryName: string) => boolean) | undefined;
389
390    constructor(private shimHost: LanguageServiceShimHost) {
391        // if shimHost is a COM object then property check will become method call with no arguments.
392        // 'in' does not have this effect.
393        if ("getModuleResolutionsForFile" in this.shimHost) {
394            this.resolveModuleNames = (moduleNames, containingFile) => {
395                const resolutionsInFile = JSON.parse(this.shimHost.getModuleResolutionsForFile!(containingFile)) as MapLike<string>; // TODO: GH#18217
396                return map(moduleNames, name => {
397                    const result = getProperty(resolutionsInFile, name);
398                    return result ? { resolvedFileName: result, extension: extensionFromPath(result), isExternalLibraryImport: false } : undefined;
399                });
400            };
401        }
402        if ("directoryExists" in this.shimHost) {
403            this.directoryExists = directoryName => this.shimHost.directoryExists(directoryName);
404        }
405        if ("getTypeReferenceDirectiveResolutionsForFile" in this.shimHost) {
406            this.resolveTypeReferenceDirectives = (typeDirectiveNames, containingFile) => {
407                const typeDirectivesForFile = JSON.parse(this.shimHost.getTypeReferenceDirectiveResolutionsForFile!(containingFile)) as MapLike<ResolvedTypeReferenceDirective>; // TODO: GH#18217
408                return map(typeDirectiveNames as (string | FileReference)[], name => getProperty(typeDirectivesForFile, isString(name) ? name : name.fileName.toLowerCase()));
409            };
410        }
411    }
412
413    public log(s: string): void {
414        if (this.loggingEnabled) {
415            this.shimHost.log(s);
416        }
417    }
418
419    public trace(s: string): void {
420        if (this.tracingEnabled) {
421            this.shimHost.trace(s);
422        }
423    }
424
425    public error(s: string): void {
426        this.shimHost.error(s);
427    }
428
429    public getProjectVersion(): string {
430        if (!this.shimHost.getProjectVersion) {
431            // shimmed host does not support getProjectVersion
432            return undefined!; // TODO: GH#18217
433        }
434
435        return this.shimHost.getProjectVersion();
436    }
437
438    public getTypeRootsVersion(): number {
439        if (!this.shimHost.getTypeRootsVersion) {
440            return 0;
441        }
442        return this.shimHost.getTypeRootsVersion();
443    }
444
445    public useCaseSensitiveFileNames(): boolean {
446        return this.shimHost.useCaseSensitiveFileNames ? this.shimHost.useCaseSensitiveFileNames() : false;
447    }
448
449    public getCompilationSettings(): CompilerOptions {
450        const settingsJson = this.shimHost.getCompilationSettings();
451        // eslint-disable-next-line no-null/no-null
452        if (settingsJson === null || settingsJson === "") {
453            throw Error("LanguageServiceShimHostAdapter.getCompilationSettings: empty compilationSettings");
454        }
455        const compilerOptions = JSON.parse(settingsJson) as CompilerOptions;
456        // permit language service to handle all files (filtering should be performed on the host side)
457        compilerOptions.allowNonTsExtensions = true;
458        return compilerOptions;
459    }
460
461    public getScriptFileNames(): string[] {
462        const encoded = this.shimHost.getScriptFileNames();
463        return JSON.parse(encoded);
464    }
465
466    public getScriptSnapshot(fileName: string): IScriptSnapshot | undefined {
467        const scriptSnapshot = this.shimHost.getScriptSnapshot(fileName);
468        return scriptSnapshot && new ScriptSnapshotShimAdapter(scriptSnapshot);
469    }
470
471    public getScriptKind(fileName: string): ScriptKind {
472        if ("getScriptKind" in this.shimHost) {
473            return this.shimHost.getScriptKind!(fileName); // TODO: GH#18217
474        }
475        else {
476            return ScriptKind.Unknown;
477        }
478    }
479
480    public getScriptVersion(fileName: string): string {
481        return this.shimHost.getScriptVersion(fileName);
482    }
483
484    public getLocalizedDiagnosticMessages() {
485        /* eslint-disable no-null/no-null */
486        const diagnosticMessagesJson = this.shimHost.getLocalizedDiagnosticMessages();
487        if (diagnosticMessagesJson === null || diagnosticMessagesJson === "") {
488            return null;
489        }
490
491        try {
492            return JSON.parse(diagnosticMessagesJson);
493        }
494        catch (e) {
495            this.log(e.description || "diagnosticMessages.generated.json has invalid JSON format");
496            return null;
497        }
498        /* eslint-enable no-null/no-null */
499    }
500
501    public getCancellationToken(): HostCancellationToken {
502        const hostCancellationToken = this.shimHost.getCancellationToken();
503        return new ThrottledCancellationToken(hostCancellationToken);
504    }
505
506    public getCurrentDirectory(): string {
507        return this.shimHost.getCurrentDirectory();
508    }
509
510    public getDirectories(path: string): string[] {
511        return JSON.parse(this.shimHost.getDirectories(path));
512    }
513
514    public getDefaultLibFileName(options: CompilerOptions): string {
515        return this.shimHost.getDefaultLibFileName(JSON.stringify(options));
516    }
517
518    public readDirectory(path: string, extensions?: readonly string[], exclude?: string[], include?: string[], depth?: number): string[] {
519        const pattern = getFileMatcherPatterns(path, exclude, include,
520            this.shimHost.useCaseSensitiveFileNames!(), this.shimHost.getCurrentDirectory()); // TODO: GH#18217
521        return JSON.parse(this.shimHost.readDirectory(
522            path,
523            JSON.stringify(extensions),
524            JSON.stringify(pattern.basePaths),
525            pattern.excludePattern,
526            pattern.includeFilePattern,
527            pattern.includeDirectoryPattern,
528            depth
529        ));
530    }
531
532    public readFile(path: string, encoding?: string): string | undefined {
533        return this.shimHost.readFile(path, encoding);
534    }
535
536    public fileExists(path: string): boolean {
537        return this.shimHost.fileExists(path);
538    }
539}
540
541/** @internal */
542export class CoreServicesShimHostAdapter implements ParseConfigHost, ModuleResolutionHost, JsTyping.TypingResolutionHost {
543
544    public directoryExists: (directoryName: string) => boolean;
545    public realpath: (path: string) => string;
546    public useCaseSensitiveFileNames: boolean;
547
548    constructor(private shimHost: CoreServicesShimHost) {
549        this.useCaseSensitiveFileNames = this.shimHost.useCaseSensitiveFileNames ? this.shimHost.useCaseSensitiveFileNames() : false;
550        if ("directoryExists" in this.shimHost) {
551            this.directoryExists = directoryName => this.shimHost.directoryExists(directoryName);
552        }
553        else {
554            this.directoryExists = undefined!; // TODO: GH#18217
555        }
556        if ("realpath" in this.shimHost) {
557            this.realpath = path => this.shimHost.realpath!(path); // TODO: GH#18217
558        }
559        else {
560            this.realpath = undefined!; // TODO: GH#18217
561        }
562    }
563
564    public readDirectory(rootDir: string, extensions: readonly string[], exclude: readonly string[], include: readonly string[], depth?: number): string[] {
565        const pattern = getFileMatcherPatterns(rootDir, exclude, include,
566            this.shimHost.useCaseSensitiveFileNames!(), this.shimHost.getCurrentDirectory()); // TODO: GH#18217
567        return JSON.parse(this.shimHost.readDirectory(
568            rootDir,
569            JSON.stringify(extensions),
570            JSON.stringify(pattern.basePaths),
571            pattern.excludePattern,
572            pattern.includeFilePattern,
573            pattern.includeDirectoryPattern,
574            depth
575        ));
576    }
577
578    public fileExists(fileName: string): boolean {
579        return this.shimHost.fileExists(fileName);
580    }
581
582    public readFile(fileName: string): string | undefined {
583        return this.shimHost.readFile(fileName);
584    }
585
586    public getDirectories(path: string): string[] {
587        return JSON.parse(this.shimHost.getDirectories(path));
588    }
589}
590
591function simpleForwardCall(logger: Logger, actionDescription: string, action: () => unknown, logPerformance: boolean): unknown {
592    let start: number | undefined;
593    if (logPerformance) {
594        logger.log(actionDescription);
595        start = timestamp();
596    }
597
598    const result = action();
599
600    if (logPerformance) {
601        const end = timestamp();
602        logger.log(`${actionDescription} completed in ${end - start!} msec`);
603        if (isString(result)) {
604            let str = result;
605            if (str.length > 128) {
606                str = str.substring(0, 128) + "...";
607            }
608            logger.log(`  result.length=${str.length}, result='${JSON.stringify(str)}'`);
609        }
610    }
611
612    return result;
613}
614
615function forwardJSONCall(logger: Logger, actionDescription: string, action: () => {} | null | undefined, logPerformance: boolean): string {
616    return forwardCall(logger, actionDescription, /*returnJson*/ true, action, logPerformance) as string;
617}
618
619function forwardCall<T>(logger: Logger, actionDescription: string, returnJson: boolean, action: () => T, logPerformance: boolean): T | string {
620    try {
621        const result = simpleForwardCall(logger, actionDescription, action, logPerformance);
622        return returnJson ? JSON.stringify({ result }) : result as T;
623    }
624    catch (err) {
625        if (err instanceof OperationCanceledException) {
626            return JSON.stringify({ canceled: true });
627        }
628        logInternalError(logger, err);
629        err.description = actionDescription;
630        return JSON.stringify({ error: err });
631    }
632}
633
634
635class ShimBase implements Shim {
636    constructor(private factory: ShimFactory) {
637        factory.registerShim(this);
638    }
639    public dispose(_dummy: {}): void {
640        this.factory.unregisterShim(this);
641    }
642}
643
644/** @internal */
645export interface RealizedDiagnostic {
646    message: string;
647    start: number;
648    length: number;
649    category: string;
650    code: number;
651    reportsUnnecessary?: {};
652    reportsDeprecated?: {};
653}
654/** @internal */
655export function realizeDiagnostics(diagnostics: readonly Diagnostic[], newLine: string): RealizedDiagnostic[] {
656    return diagnostics.map(d => realizeDiagnostic(d, newLine));
657}
658
659function realizeDiagnostic(diagnostic: Diagnostic, newLine: string): RealizedDiagnostic {
660    return {
661        message: flattenDiagnosticMessageText(diagnostic.messageText, newLine),
662        start: diagnostic.start!, // TODO: GH#18217
663        length: diagnostic.length!, // TODO: GH#18217
664        category: diagnosticCategoryName(diagnostic),
665        code: diagnostic.code,
666        reportsUnnecessary: diagnostic.reportsUnnecessary,
667        reportsDeprecated: diagnostic.reportsDeprecated
668    };
669}
670
671class LanguageServiceShimObject extends ShimBase implements LanguageServiceShim {
672    private logger: Logger;
673    private logPerformance = false;
674
675    constructor(factory: ShimFactory,
676        private host: LanguageServiceShimHost,
677        public languageService: LanguageService) {
678        super(factory);
679        this.logger = this.host;
680    }
681
682    public forwardJSONCall(actionDescription: string, action: () => {} | null | undefined): string {
683        return forwardJSONCall(this.logger, actionDescription, action, this.logPerformance);
684    }
685
686    /// DISPOSE
687
688    /**
689     * Ensure (almost) deterministic release of internal Javascript resources when
690     * some external native objects holds onto us (e.g. Com/Interop).
691     */
692    public dispose(dummy: {}): void {
693        this.logger.log("dispose()");
694        this.languageService.dispose();
695        this.languageService = null!; // eslint-disable-line no-null/no-null
696
697        // force a GC
698        if (debugObjectHost && debugObjectHost.CollectGarbage) {
699            debugObjectHost.CollectGarbage();
700            this.logger.log("CollectGarbage()");
701        }
702
703        this.logger = null!; // eslint-disable-line no-null/no-null
704
705        super.dispose(dummy);
706    }
707
708    /// REFRESH
709
710    /**
711     * Update the list of scripts known to the compiler
712     */
713    public refresh(throwOnError: boolean): void {
714        this.forwardJSONCall(
715            `refresh(${throwOnError})`,
716            () => null // eslint-disable-line no-null/no-null
717        );
718    }
719
720    public cleanupSemanticCache(): void {
721        this.forwardJSONCall(
722            "cleanupSemanticCache()",
723            () => {
724                this.languageService.cleanupSemanticCache();
725                return null; // eslint-disable-line no-null/no-null
726            });
727    }
728
729    private realizeDiagnostics(diagnostics: readonly Diagnostic[]): { message: string; start: number; length: number; category: string; }[] {
730        const newLine = getNewLineOrDefaultFromHost(this.host);
731        return realizeDiagnostics(diagnostics, newLine);
732    }
733
734    public getSyntacticClassifications(fileName: string, start: number, length: number): string {
735        return this.forwardJSONCall(
736            `getSyntacticClassifications('${fileName}', ${start}, ${length})`,
737            () => this.languageService.getSyntacticClassifications(fileName, createTextSpan(start, length))
738        );
739    }
740
741    public getSemanticClassifications(fileName: string, start: number, length: number): string {
742        return this.forwardJSONCall(
743            `getSemanticClassifications('${fileName}', ${start}, ${length})`,
744            () => this.languageService.getSemanticClassifications(fileName, createTextSpan(start, length))
745        );
746    }
747
748    public getEncodedSyntacticClassifications(fileName: string, start: number, length: number): string {
749        return this.forwardJSONCall(
750            `getEncodedSyntacticClassifications('${fileName}', ${start}, ${length})`,
751            // directly serialize the spans out to a string.  This is much faster to decode
752            // on the managed side versus a full JSON array.
753            () => convertClassifications(this.languageService.getEncodedSyntacticClassifications(fileName, createTextSpan(start, length)))
754        );
755    }
756
757    public getEncodedSemanticClassifications(fileName: string, start: number, length: number): string {
758        return this.forwardJSONCall(
759            `getEncodedSemanticClassifications('${fileName}', ${start}, ${length})`,
760            // directly serialize the spans out to a string.  This is much faster to decode
761            // on the managed side versus a full JSON array.
762            () => convertClassifications(this.languageService.getEncodedSemanticClassifications(fileName, createTextSpan(start, length)))
763        );
764    }
765
766    public getSyntacticDiagnostics(fileName: string): string {
767        return this.forwardJSONCall(
768            `getSyntacticDiagnostics('${fileName}')`,
769            () => {
770                const diagnostics = this.languageService.getSyntacticDiagnostics(fileName);
771                return this.realizeDiagnostics(diagnostics);
772            });
773    }
774
775    public getSemanticDiagnostics(fileName: string): string {
776        return this.forwardJSONCall(
777            `getSemanticDiagnostics('${fileName}')`,
778            () => {
779                const diagnostics = this.languageService.getSemanticDiagnostics(fileName);
780                return this.realizeDiagnostics(diagnostics);
781            });
782    }
783
784    public getSuggestionDiagnostics(fileName: string): string {
785        return this.forwardJSONCall(`getSuggestionDiagnostics('${fileName}')`, () => this.realizeDiagnostics(this.languageService.getSuggestionDiagnostics(fileName)));
786    }
787
788    public getCompilerOptionsDiagnostics(): string {
789        return this.forwardJSONCall(
790            "getCompilerOptionsDiagnostics()",
791            () => {
792                const diagnostics = this.languageService.getCompilerOptionsDiagnostics();
793                return this.realizeDiagnostics(diagnostics);
794            });
795    }
796
797    /// QUICKINFO
798
799    /**
800     * Computes a string representation of the type at the requested position
801     * in the active file.
802     */
803    public getQuickInfoAtPosition(fileName: string, position: number): string {
804        return this.forwardJSONCall(
805            `getQuickInfoAtPosition('${fileName}', ${position})`,
806            () => this.languageService.getQuickInfoAtPosition(fileName, position)
807        );
808    }
809
810
811    /// NAMEORDOTTEDNAMESPAN
812
813    /**
814     * Computes span information of the name or dotted name at the requested position
815     * in the active file.
816     */
817    public getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): string {
818        return this.forwardJSONCall(
819            `getNameOrDottedNameSpan('${fileName}', ${startPos}, ${endPos})`,
820            () => this.languageService.getNameOrDottedNameSpan(fileName, startPos, endPos)
821        );
822    }
823
824    /**
825     * STATEMENTSPAN
826     * Computes span information of statement at the requested position in the active file.
827     */
828    public getBreakpointStatementAtPosition(fileName: string, position: number): string {
829        return this.forwardJSONCall(
830            `getBreakpointStatementAtPosition('${fileName}', ${position})`,
831            () => this.languageService.getBreakpointStatementAtPosition(fileName, position)
832        );
833    }
834
835    /// SIGNATUREHELP
836
837    public getSignatureHelpItems(fileName: string, position: number, options: SignatureHelpItemsOptions | undefined): string {
838        return this.forwardJSONCall(
839            `getSignatureHelpItems('${fileName}', ${position})`,
840            () => this.languageService.getSignatureHelpItems(fileName, position, options)
841        );
842    }
843
844    /// GOTO DEFINITION
845
846    /**
847     * Computes the definition location and file for the symbol
848     * at the requested position.
849     */
850    public getDefinitionAtPosition(fileName: string, position: number): string {
851        return this.forwardJSONCall(
852            `getDefinitionAtPosition('${fileName}', ${position})`,
853            () => this.languageService.getDefinitionAtPosition(fileName, position)
854        );
855    }
856
857    /**
858     * Computes the definition location and file for the symbol
859     * at the requested position.
860     */
861    public getDefinitionAndBoundSpan(fileName: string, position: number): string {
862        return this.forwardJSONCall(
863            `getDefinitionAndBoundSpan('${fileName}', ${position})`,
864            () => this.languageService.getDefinitionAndBoundSpan(fileName, position)
865        );
866    }
867
868    /// GOTO Type
869
870    /**
871     * Computes the definition location of the type of the symbol
872     * at the requested position.
873     */
874    public getTypeDefinitionAtPosition(fileName: string, position: number): string {
875        return this.forwardJSONCall(
876            `getTypeDefinitionAtPosition('${fileName}', ${position})`,
877            () => this.languageService.getTypeDefinitionAtPosition(fileName, position)
878        );
879    }
880
881    /// GOTO Implementation
882
883    /**
884     * Computes the implementation location of the symbol
885     * at the requested position.
886     */
887    public getImplementationAtPosition(fileName: string, position: number): string {
888        return this.forwardJSONCall(
889            `getImplementationAtPosition('${fileName}', ${position})`,
890            () => this.languageService.getImplementationAtPosition(fileName, position)
891        );
892    }
893
894    public getRenameInfo(fileName: string, position: number, preferences: UserPreferences): string {
895        return this.forwardJSONCall(
896            `getRenameInfo('${fileName}', ${position})`,
897            () => this.languageService.getRenameInfo(fileName, position, preferences)
898        );
899    }
900
901    public getSmartSelectionRange(fileName: string, position: number): string {
902        return this.forwardJSONCall(
903            `getSmartSelectionRange('${fileName}', ${position})`,
904            () => this.languageService.getSmartSelectionRange(fileName, position)
905        );
906    }
907
908    public findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): string {
909        return this.forwardJSONCall(
910            `findRenameLocations('${fileName}', ${position}, ${findInStrings}, ${findInComments}, ${providePrefixAndSuffixTextForRename})`,
911            () => this.languageService.findRenameLocations(fileName, position, findInStrings, findInComments, providePrefixAndSuffixTextForRename)
912        );
913    }
914
915    /// GET BRACE MATCHING
916    public getBraceMatchingAtPosition(fileName: string, position: number): string {
917        return this.forwardJSONCall(
918            `getBraceMatchingAtPosition('${fileName}', ${position})`,
919            () => this.languageService.getBraceMatchingAtPosition(fileName, position)
920        );
921    }
922
923    public isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): string {
924        return this.forwardJSONCall(
925            `isValidBraceCompletionAtPosition('${fileName}', ${position}, ${openingBrace})`,
926            () => this.languageService.isValidBraceCompletionAtPosition(fileName, position, openingBrace)
927        );
928    }
929
930    public getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): string {
931        return this.forwardJSONCall(
932            `getSpanOfEnclosingComment('${fileName}', ${position})`,
933            () => this.languageService.getSpanOfEnclosingComment(fileName, position, onlyMultiLine)
934        );
935    }
936
937    /// GET SMART INDENT
938    public getIndentationAtPosition(fileName: string, position: number, options: string /*Services.EditorOptions*/): string {
939        return this.forwardJSONCall(
940            `getIndentationAtPosition('${fileName}', ${position})`,
941            () => {
942                const localOptions: EditorOptions = JSON.parse(options);
943                return this.languageService.getIndentationAtPosition(fileName, position, localOptions);
944            });
945    }
946
947    /// GET REFERENCES
948
949    public getReferencesAtPosition(fileName: string, position: number): string {
950        return this.forwardJSONCall(
951            `getReferencesAtPosition('${fileName}', ${position})`,
952            () => this.languageService.getReferencesAtPosition(fileName, position)
953        );
954    }
955
956    public findReferences(fileName: string, position: number): string {
957        return this.forwardJSONCall(
958            `findReferences('${fileName}', ${position})`,
959            () => this.languageService.findReferences(fileName, position)
960        );
961    }
962
963    public getFileReferences(fileName: string) {
964        return this.forwardJSONCall(
965            `getFileReferences('${fileName})`,
966            () => this.languageService.getFileReferences(fileName)
967        );
968    }
969
970    public getOccurrencesAtPosition(fileName: string, position: number): string {
971        return this.forwardJSONCall(
972            `getOccurrencesAtPosition('${fileName}', ${position})`,
973            () => this.languageService.getOccurrencesAtPosition(fileName, position)
974        );
975    }
976
977    public getDocumentHighlights(fileName: string, position: number, filesToSearch: string): string {
978        return this.forwardJSONCall(
979            `getDocumentHighlights('${fileName}', ${position})`,
980            () => {
981                const results = this.languageService.getDocumentHighlights(fileName, position, JSON.parse(filesToSearch));
982                // workaround for VS document highlighting issue - keep only items from the initial file
983                const normalizedName = toFileNameLowerCase(normalizeSlashes(fileName));
984                return filter(results, r => toFileNameLowerCase(normalizeSlashes(r.fileName)) === normalizedName);
985            });
986    }
987
988    /// COMPLETION LISTS
989
990    /**
991     * Get a string based representation of the completions
992     * to provide at the given source position and providing a member completion
993     * list if requested.
994     */
995    public getCompletionsAtPosition(fileName: string, position: number, preferences: GetCompletionsAtPositionOptions | undefined, formattingSettings: FormatCodeSettings | undefined) {
996        return this.forwardJSONCall(
997            `getCompletionsAtPosition('${fileName}', ${position}, ${preferences}, ${formattingSettings})`,
998            () => this.languageService.getCompletionsAtPosition(fileName, position, preferences, formattingSettings)
999        );
1000    }
1001
1002    /** Get a string based representation of a completion list entry details */
1003    public getCompletionEntryDetails(fileName: string, position: number, entryName: string, formatOptions: string/*Services.FormatCodeOptions*/ | undefined, source: string | undefined, preferences: UserPreferences | undefined, data: CompletionEntryData | undefined) {
1004        return this.forwardJSONCall(
1005            `getCompletionEntryDetails('${fileName}', ${position}, '${entryName}')`,
1006            () => {
1007                const localOptions: FormatCodeOptions = formatOptions === undefined ? undefined : JSON.parse(formatOptions);
1008                return this.languageService.getCompletionEntryDetails(fileName, position, entryName, localOptions, source, preferences, data);
1009            }
1010        );
1011    }
1012
1013    public getFormattingEditsForRange(fileName: string, start: number, end: number, options: string/*Services.FormatCodeOptions*/): string {
1014        return this.forwardJSONCall(
1015            `getFormattingEditsForRange('${fileName}', ${start}, ${end})`,
1016            () => {
1017                const localOptions: FormatCodeOptions = JSON.parse(options);
1018                return this.languageService.getFormattingEditsForRange(fileName, start, end, localOptions);
1019            });
1020    }
1021
1022    public getFormattingEditsForDocument(fileName: string, options: string/*Services.FormatCodeOptions*/): string {
1023        return this.forwardJSONCall(
1024            `getFormattingEditsForDocument('${fileName}')`,
1025            () => {
1026                const localOptions: FormatCodeOptions = JSON.parse(options);
1027                return this.languageService.getFormattingEditsForDocument(fileName, localOptions);
1028            });
1029    }
1030
1031    public getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: string/*Services.FormatCodeOptions*/): string {
1032        return this.forwardJSONCall(
1033            `getFormattingEditsAfterKeystroke('${fileName}', ${position}, '${key}')`,
1034            () => {
1035                const localOptions: FormatCodeOptions = JSON.parse(options);
1036                return this.languageService.getFormattingEditsAfterKeystroke(fileName, position, key, localOptions);
1037            });
1038    }
1039
1040    public getDocCommentTemplateAtPosition(fileName: string, position: number, options?: DocCommentTemplateOptions): string {
1041        return this.forwardJSONCall(
1042            `getDocCommentTemplateAtPosition('${fileName}', ${position})`,
1043            () => this.languageService.getDocCommentTemplateAtPosition(fileName, position, options)
1044        );
1045    }
1046
1047    /// NAVIGATE TO
1048
1049    /** Return a list of symbols that are interesting to navigate to */
1050    public getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string): string {
1051        return this.forwardJSONCall(
1052            `getNavigateToItems('${searchValue}', ${maxResultCount}, ${fileName})`,
1053            () => this.languageService.getNavigateToItems(searchValue, maxResultCount, fileName)
1054        );
1055    }
1056
1057    public getNavigationBarItems(fileName: string): string {
1058        return this.forwardJSONCall(
1059            `getNavigationBarItems('${fileName}')`,
1060            () => this.languageService.getNavigationBarItems(fileName)
1061        );
1062    }
1063
1064    public getNavigationTree(fileName: string): string {
1065        return this.forwardJSONCall(
1066            `getNavigationTree('${fileName}')`,
1067            () => this.languageService.getNavigationTree(fileName)
1068        );
1069    }
1070
1071    public getOutliningSpans(fileName: string): string {
1072        return this.forwardJSONCall(
1073            `getOutliningSpans('${fileName}')`,
1074            () => this.languageService.getOutliningSpans(fileName)
1075        );
1076    }
1077
1078    public getTodoComments(fileName: string, descriptors: string): string {
1079        return this.forwardJSONCall(
1080            `getTodoComments('${fileName}')`,
1081            () => this.languageService.getTodoComments(fileName, JSON.parse(descriptors))
1082        );
1083    }
1084
1085    /// CALL HIERARCHY
1086
1087    public prepareCallHierarchy(fileName: string, position: number): string {
1088        return this.forwardJSONCall(
1089            `prepareCallHierarchy('${fileName}', ${position})`,
1090            () => this.languageService.prepareCallHierarchy(fileName, position)
1091        );
1092    }
1093
1094    public provideCallHierarchyIncomingCalls(fileName: string, position: number): string {
1095        return this.forwardJSONCall(
1096            `provideCallHierarchyIncomingCalls('${fileName}', ${position})`,
1097            () => this.languageService.provideCallHierarchyIncomingCalls(fileName, position)
1098        );
1099    }
1100
1101    public provideCallHierarchyOutgoingCalls(fileName: string, position: number): string {
1102        return this.forwardJSONCall(
1103            `provideCallHierarchyOutgoingCalls('${fileName}', ${position})`,
1104            () => this.languageService.provideCallHierarchyOutgoingCalls(fileName, position)
1105        );
1106    }
1107
1108    public provideInlayHints(fileName: string, span: TextSpan, preference: UserPreferences | undefined): string {
1109        return this.forwardJSONCall(
1110            `provideInlayHints('${fileName}', '${JSON.stringify(span)}', ${JSON.stringify(preference)})`,
1111            () => this.languageService.provideInlayHints(fileName, span, preference)
1112        );
1113    }
1114
1115    /// Emit
1116    public getEmitOutput(fileName: string): string {
1117        return this.forwardJSONCall(
1118            `getEmitOutput('${fileName}')`,
1119            () => {
1120                const { diagnostics, ...rest } = this.languageService.getEmitOutput(fileName);
1121                return { ...rest, diagnostics: this.realizeDiagnostics(diagnostics) };
1122            }
1123        );
1124    }
1125
1126    public getEmitOutputObject(fileName: string): EmitOutput {
1127        return forwardCall(
1128            this.logger,
1129            `getEmitOutput('${fileName}')`,
1130            /*returnJson*/ false,
1131            () => this.languageService.getEmitOutput(fileName),
1132            this.logPerformance) as EmitOutput;
1133    }
1134
1135    public toggleLineComment(fileName: string, textRange: TextRange): string {
1136        return this.forwardJSONCall(
1137            `toggleLineComment('${fileName}', '${JSON.stringify(textRange)}')`,
1138            () => this.languageService.toggleLineComment(fileName, textRange)
1139        );
1140    }
1141
1142    public toggleMultilineComment(fileName: string, textRange: TextRange): string {
1143        return this.forwardJSONCall(
1144            `toggleMultilineComment('${fileName}', '${JSON.stringify(textRange)}')`,
1145            () => this.languageService.toggleMultilineComment(fileName, textRange)
1146        );
1147    }
1148
1149    public commentSelection(fileName: string, textRange: TextRange): string {
1150        return this.forwardJSONCall(
1151            `commentSelection('${fileName}', '${JSON.stringify(textRange)}')`,
1152            () => this.languageService.commentSelection(fileName, textRange)
1153        );
1154    }
1155
1156    public uncommentSelection(fileName: string, textRange: TextRange): string {
1157        return this.forwardJSONCall(
1158            `uncommentSelection('${fileName}', '${JSON.stringify(textRange)}')`,
1159            () => this.languageService.uncommentSelection(fileName, textRange)
1160        );
1161    }
1162}
1163
1164function convertClassifications(classifications: Classifications): { spans: string, endOfLineState: EndOfLineState } {
1165    return { spans: classifications.spans.join(","), endOfLineState: classifications.endOfLineState };
1166}
1167
1168class ClassifierShimObject extends ShimBase implements ClassifierShim {
1169    public classifier: Classifier;
1170    private logPerformance = false;
1171
1172    constructor(factory: ShimFactory, private logger: Logger) {
1173        super(factory);
1174        this.classifier = createClassifier();
1175    }
1176
1177    public getEncodedLexicalClassifications(text: string, lexState: EndOfLineState, syntacticClassifierAbsent = false): string {
1178        return forwardJSONCall(this.logger, "getEncodedLexicalClassifications",
1179            () => convertClassifications(this.classifier.getEncodedLexicalClassifications(text, lexState, syntacticClassifierAbsent)),
1180            this.logPerformance);
1181    }
1182
1183    /// COLORIZATION
1184    public getClassificationsForLine(text: string, lexState: EndOfLineState, classifyKeywordsInGenerics = false): string {
1185        const classification = this.classifier.getClassificationsForLine(text, lexState, classifyKeywordsInGenerics);
1186        let result = "";
1187        for (const item of classification.entries) {
1188            result += item.length + "\n";
1189            result += item.classification + "\n";
1190        }
1191        result += classification.finalLexState;
1192        return result;
1193    }
1194}
1195
1196class CoreServicesShimObject extends ShimBase implements CoreServicesShim {
1197    private logPerformance = false;
1198    private safeList: JsTyping.SafeList | undefined;
1199
1200    constructor(factory: ShimFactory, public readonly logger: Logger, private readonly host: CoreServicesShimHostAdapter) {
1201        super(factory);
1202    }
1203
1204    private forwardJSONCall(actionDescription: string, action: () => {}): string {
1205        return forwardJSONCall(this.logger, actionDescription, action, this.logPerformance);
1206    }
1207
1208    public resolveModuleName(fileName: string, moduleName: string, compilerOptionsJson: string): string {
1209        return this.forwardJSONCall(`resolveModuleName('${fileName}')`, () => {
1210            const compilerOptions = JSON.parse(compilerOptionsJson) as CompilerOptions;
1211            const result = resolveModuleName(moduleName, normalizeSlashes(fileName), compilerOptions, this.host);
1212            let resolvedFileName = result.resolvedModule ? result.resolvedModule.resolvedFileName : undefined;
1213            if (result.resolvedModule && result.resolvedModule.extension !== Extension.Ts && result.resolvedModule.extension !== Extension.Tsx && result.resolvedModule.extension !== Extension.Dts &&
1214                result.resolvedModule.extension !== Extension.Ets && result.resolvedModule.extension !== Extension.Dets) {
1215                resolvedFileName = undefined;
1216            }
1217
1218            return {
1219                resolvedFileName,
1220                failedLookupLocations: result.failedLookupLocations,
1221                affectingLocations: result.affectingLocations,
1222            };
1223        });
1224    }
1225
1226    public resolveTypeReferenceDirective(fileName: string, typeReferenceDirective: string, compilerOptionsJson: string): string {
1227        return this.forwardJSONCall(`resolveTypeReferenceDirective(${fileName})`, () => {
1228            const compilerOptions = JSON.parse(compilerOptionsJson) as CompilerOptions;
1229            const result = resolveTypeReferenceDirective(typeReferenceDirective, normalizeSlashes(fileName), compilerOptions, this.host);
1230            return {
1231                resolvedFileName: result.resolvedTypeReferenceDirective ? result.resolvedTypeReferenceDirective.resolvedFileName : undefined,
1232                primary: result.resolvedTypeReferenceDirective ? result.resolvedTypeReferenceDirective.primary : true,
1233                failedLookupLocations: result.failedLookupLocations
1234            };
1235        });
1236    }
1237
1238    public getPreProcessedFileInfo(fileName: string, sourceTextSnapshot: IScriptSnapshot): string {
1239        return this.forwardJSONCall(
1240            `getPreProcessedFileInfo('${fileName}')`,
1241            () => {
1242                // for now treat files as JavaScript
1243                const result = preProcessFile(getSnapshotText(sourceTextSnapshot), /* readImportFiles */ true, /* detectJavaScriptImports */ true);
1244                return {
1245                    referencedFiles: this.convertFileReferences(result.referencedFiles),
1246                    importedFiles: this.convertFileReferences(result.importedFiles),
1247                    ambientExternalModules: result.ambientExternalModules,
1248                    isLibFile: result.isLibFile,
1249                    typeReferenceDirectives: this.convertFileReferences(result.typeReferenceDirectives),
1250                    libReferenceDirectives: this.convertFileReferences(result.libReferenceDirectives)
1251                };
1252            });
1253    }
1254
1255    public getAutomaticTypeDirectiveNames(compilerOptionsJson: string): string {
1256        return this.forwardJSONCall(
1257            `getAutomaticTypeDirectiveNames('${compilerOptionsJson}')`,
1258            () => {
1259                const compilerOptions = JSON.parse(compilerOptionsJson) as CompilerOptions;
1260                return getAutomaticTypeDirectiveNames(compilerOptions, this.host);
1261            }
1262        );
1263    }
1264
1265    private convertFileReferences(refs: FileReference[]): ShimsFileReference[] | undefined {
1266        if (!refs) {
1267            return undefined;
1268        }
1269        const result: ShimsFileReference[] = [];
1270        for (const ref of refs) {
1271            result.push({
1272                path: normalizeSlashes(ref.fileName),
1273                position: ref.pos,
1274                length: ref.end - ref.pos
1275            });
1276        }
1277        return result;
1278    }
1279
1280    public getTSConfigFileInfo(fileName: string, sourceTextSnapshot: IScriptSnapshot): string {
1281        return this.forwardJSONCall(
1282            `getTSConfigFileInfo('${fileName}')`,
1283            () => {
1284                const result = parseJsonText(fileName, getSnapshotText(sourceTextSnapshot));
1285                const normalizedFileName = normalizeSlashes(fileName);
1286                const configFile = parseJsonSourceFileConfigFileContent(result, this.host, getDirectoryPath(normalizedFileName), /*existingOptions*/ {}, normalizedFileName);
1287
1288                return {
1289                    options: configFile.options,
1290                    typeAcquisition: configFile.typeAcquisition,
1291                    files: configFile.fileNames,
1292                    raw: configFile.raw,
1293                    errors: realizeDiagnostics([...result.parseDiagnostics, ...configFile.errors], "\r\n")
1294                };
1295            });
1296    }
1297
1298    public getDefaultCompilationSettings(): string {
1299        return this.forwardJSONCall(
1300            "getDefaultCompilationSettings()",
1301            () => getDefaultCompilerOptions()
1302        );
1303    }
1304
1305    public discoverTypings(discoverTypingsJson: string): string {
1306        const getCanonicalFileName = createGetCanonicalFileName(/*useCaseSensitivefileNames:*/ false);
1307        return this.forwardJSONCall("discoverTypings()", () => {
1308            const info = JSON.parse(discoverTypingsJson) as DiscoverTypingsInfo;
1309            if (this.safeList === undefined) {
1310                this.safeList = JsTyping.loadSafeList(this.host, toPath(info.safeListPath, info.safeListPath, getCanonicalFileName));
1311            }
1312            return JsTyping.discoverTypings(
1313                this.host,
1314                msg => this.logger.log(msg),
1315                info.fileNames,
1316                toPath(info.projectRootPath, info.projectRootPath, getCanonicalFileName),
1317                this.safeList,
1318                info.packageNameToTypingLocation,
1319                info.typeAcquisition,
1320                info.unresolvedImports,
1321                info.typesRegistry,
1322                emptyOptions);
1323        });
1324    }
1325}
1326
1327/** @internal */
1328export class TypeScriptServicesFactory implements ShimFactory {
1329    private _shims: Shim[] = [];
1330    private documentRegistry: DocumentRegistry | undefined;
1331
1332    /*
1333     * Returns script API version.
1334     */
1335    public getServicesVersion(): string {
1336        return servicesVersion;
1337    }
1338
1339    public createLanguageServiceShim(host: LanguageServiceShimHost): LanguageServiceShim {
1340        try {
1341            if (this.documentRegistry === undefined) {
1342                this.documentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(), host.getCurrentDirectory());
1343            }
1344            const hostAdapter = new LanguageServiceShimHostAdapter(host);
1345            const languageService = createLanguageService(hostAdapter, this.documentRegistry, /*syntaxOnly*/ false);
1346            return new LanguageServiceShimObject(this, host, languageService);
1347        }
1348        catch (err) {
1349            logInternalError(host, err);
1350            throw err;
1351        }
1352    }
1353
1354    public createClassifierShim(logger: Logger): ClassifierShim {
1355        try {
1356            return new ClassifierShimObject(this, logger);
1357        }
1358        catch (err) {
1359            logInternalError(logger, err);
1360            throw err;
1361        }
1362    }
1363
1364    public createCoreServicesShim(host: CoreServicesShimHost): CoreServicesShim {
1365        try {
1366            const adapter = new CoreServicesShimHostAdapter(host);
1367            return new CoreServicesShimObject(this, host as Logger, adapter);
1368        }
1369        catch (err) {
1370            logInternalError(host as Logger, err);
1371            throw err;
1372        }
1373    }
1374
1375    public close(): void {
1376        // Forget all the registered shims
1377        clear(this._shims);
1378        this.documentRegistry = undefined;
1379    }
1380
1381    public registerShim(shim: Shim): void {
1382        this._shims.push(shim);
1383    }
1384
1385    public unregisterShim(shim: Shim): void {
1386        for (let i = 0; i < this._shims.length; i++) {
1387            if (this._shims[i] === shim) {
1388                delete this._shims[i];
1389                return;
1390            }
1391        }
1392
1393        throw new Error("Invalid operation");
1394    }
1395}
1396
1397/* eslint-enable local/no-in-operator */
1398