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