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