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