1namespace ts.server { 2 3 export enum ProjectKind { 4 Inferred, 5 Configured, 6 External, 7 AutoImportProvider, 8 Auxiliary, 9 } 10 11 /* @internal */ 12 export type Mutable<T> = { -readonly [K in keyof T]: T[K]; }; 13 14 /* @internal */ 15 export function countEachFileTypes(infos: ScriptInfo[], includeSizes = false): FileStats { 16 const result: Mutable<FileStats> = { 17 js: 0, jsSize: 0, 18 jsx: 0, jsxSize: 0, 19 ts: 0, tsSize: 0, 20 tsx: 0, tsxSize: 0, 21 dts: 0, dtsSize: 0, 22 deferred: 0, deferredSize: 0, 23 ets: 0, etsSize: 0, 24 dets: 0, detsSize: 0, 25 }; 26 for (const info of infos) { 27 const fileSize = includeSizes ? info.getTelemetryFileSize() : 0; 28 switch (info.scriptKind) { 29 case ScriptKind.JS: 30 result.js += 1; 31 result.jsSize! += fileSize; 32 break; 33 case ScriptKind.JSX: 34 result.jsx += 1; 35 result.jsxSize! += fileSize; 36 break; 37 case ScriptKind.TS: 38 if (isDeclarationFileName(info.fileName)) { 39 result.dts += 1; 40 result.dtsSize! += fileSize; 41 } 42 else { 43 result.ts += 1; 44 result.tsSize! += fileSize; 45 } 46 break; 47 case ScriptKind.TSX: 48 result.tsx += 1; 49 result.tsxSize! += fileSize; 50 break; 51 case ScriptKind.Deferred: 52 result.deferred += 1; 53 result.deferredSize! += fileSize; 54 break; 55 case ScriptKind.ETS: 56 if (fileExtensionIs(info.fileName, Extension.Dets)) { 57 result.dets += 1; 58 result.detsSize! += fileSize; 59 } 60 else { 61 result.ets += 1; 62 result.etsSize! += fileSize; 63 } 64 break; 65 } 66 } 67 return result; 68 } 69 70 function hasOneOrMoreJsAndNoTsFiles(project: Project) { 71 const counts = countEachFileTypes(project.getScriptInfos()); 72 return counts.js > 0 && counts.ts === 0 && counts.tsx === 0; 73 } 74 75 export function allRootFilesAreJsOrDts(project: Project): boolean { 76 const counts = countEachFileTypes(project.getRootScriptInfos()); 77 return counts.ts === 0 && counts.tsx === 0; 78 } 79 80 export function allFilesAreJsOrDts(project: Project): boolean { 81 const counts = countEachFileTypes(project.getScriptInfos()); 82 return counts.ts === 0 && counts.tsx === 0; 83 } 84 85 /* @internal */ 86 export function hasNoTypeScriptSource(fileNames: string[]): boolean { 87 return !fileNames.some(fileName => (fileExtensionIs(fileName, Extension.Ts) && !isDeclarationFileName(fileName)) || fileExtensionIs(fileName, Extension.Tsx) || (fileExtensionIs(fileName, Extension.Ets) && !isDeclarationFileName(fileName))); 88 } 89 90 /* @internal */ 91 export interface ProjectFilesWithTSDiagnostics extends protocol.ProjectFiles { 92 projectErrors: readonly Diagnostic[]; 93 } 94 95 export interface PluginCreateInfo { 96 project: Project; 97 languageService: LanguageService; 98 languageServiceHost: LanguageServiceHost; 99 serverHost: ServerHost; 100 session?: Session<unknown>; 101 config: any; 102 } 103 104 export interface PluginModule { 105 create(createInfo: PluginCreateInfo): LanguageService; 106 getExternalFiles?(proj: Project): string[]; 107 onConfigurationChanged?(config: any): void; 108 } 109 110 export interface PluginModuleWithName { 111 name: string; 112 module: PluginModule; 113 } 114 115 export type PluginModuleFactory = (mod: { typescript: typeof ts }) => PluginModule; 116 117 /* @internal */ 118 export interface BeginEnablePluginResult { 119 pluginConfigEntry: PluginImport; 120 pluginConfigOverrides: Map<any> | undefined; 121 resolvedModule: PluginModuleFactory | undefined; 122 errorLogs: string[] | undefined; 123 } 124 125 /** 126 * The project root can be script info - if root is present, 127 * or it could be just normalized path if root wasn't present on the host(only for non inferred project) 128 */ 129 /* @internal */ 130 export interface ProjectRootFile { 131 fileName: NormalizedPath; 132 info?: ScriptInfo; 133 } 134 135 interface GeneratedFileWatcher { 136 generatedFilePath: Path; 137 watcher: FileWatcher; 138 } 139 type GeneratedFileWatcherMap = GeneratedFileWatcher | ESMap<Path, GeneratedFileWatcher>; 140 function isGeneratedFileWatcher(watch: GeneratedFileWatcherMap): watch is GeneratedFileWatcher { 141 return (watch as GeneratedFileWatcher).generatedFilePath !== undefined; 142 } 143 144 /*@internal*/ 145 export interface EmitResult { 146 emitSkipped: boolean; 147 diagnostics: readonly Diagnostic[]; 148 } 149 150 export abstract class Project implements LanguageServiceHost, ModuleResolutionHost { 151 private rootFiles: ScriptInfo[] = []; 152 private rootFilesMap = new Map<string, ProjectRootFile>(); 153 private program: Program | undefined; 154 private externalFiles: SortedReadonlyArray<string> | undefined; 155 private missingFilesMap: ESMap<Path, FileWatcher> | undefined; 156 private generatedFilesMap: GeneratedFileWatcherMap | undefined; 157 158 /*@internal*/ 159 protected readonly plugins: PluginModuleWithName[] = []; 160 161 /*@internal*/ 162 /** 163 * This is map from files to unresolved imports in it 164 * Maop does not contain entries for files that do not have unresolved imports 165 * This helps in containing the set of files to invalidate 166 */ 167 cachedUnresolvedImportsPerFile = new Map<Path, readonly string[]>(); 168 169 /*@internal*/ 170 lastCachedUnresolvedImportsList: SortedReadonlyArray<string> | undefined; 171 /*@internal*/ 172 private hasAddedorRemovedFiles = false; 173 /*@internal*/ 174 private hasAddedOrRemovedSymlinks = false; 175 176 /*@internal*/ 177 lastFileExceededProgramSize: string | undefined; 178 179 // wrapper over the real language service that will suppress all semantic operations 180 protected languageService: LanguageService; 181 182 public languageServiceEnabled: boolean; 183 184 readonly trace?: (s: string) => void; 185 readonly realpath?: (path: string) => string; 186 187 /*@internal*/ 188 hasInvalidatedResolutions: HasInvalidatedResolutions | undefined; 189 190 /*@internal*/ 191 resolutionCache: ResolutionCache; 192 193 private builderState: BuilderState | undefined; 194 /** 195 * Set of files names that were updated since the last call to getChangesSinceVersion. 196 */ 197 private updatedFileNames: Set<string> | undefined; 198 /** 199 * Set of files that was returned from the last call to getChangesSinceVersion. 200 */ 201 private lastReportedFileNames: ESMap<string, boolean> | undefined; 202 /** 203 * Last version that was reported. 204 */ 205 private lastReportedVersion = 0; 206 /** 207 * Current project's program version. (incremented everytime new program is created that is not complete reuse from the old one) 208 * This property is changed in 'updateGraph' based on the set of files in program 209 */ 210 private projectProgramVersion = 0; 211 /** 212 * Current version of the project state. It is changed when: 213 * - new root file was added/removed 214 * - edit happen in some file that is currently included in the project. 215 * This property is different from projectStructureVersion since in most cases edits don't affect set of files in the project 216 */ 217 private projectStateVersion = 0; 218 219 protected projectErrors: Diagnostic[] | undefined; 220 221 protected isInitialLoadPending: () => boolean = returnFalse; 222 223 /*@internal*/ 224 dirty = false; 225 226 /*@internal*/ 227 typingFiles: SortedReadonlyArray<string> = emptyArray; 228 229 /*@internal*/ 230 originalConfiguredProjects: Set<NormalizedPath> | undefined; 231 232 /*@internal*/ 233 private packageJsonsForAutoImport: Set<string> | undefined; 234 235 /*@internal*/ 236 private noDtsResolutionProject?: AuxiliaryProject | undefined; 237 238 /*@internal*/ 239 getResolvedProjectReferenceToRedirect(_fileName: string): ResolvedProjectReference | undefined { 240 return undefined; 241 } 242 243 /* @internal */ useSourceOfProjectReferenceRedirect?(): boolean; 244 /* @internal */ getParsedCommandLine?(fileName: string): ParsedCommandLine | undefined; 245 246 private readonly cancellationToken: ThrottledCancellationToken; 247 248 public isNonTsProject() { 249 updateProjectIfDirty(this); 250 return allFilesAreJsOrDts(this); 251 } 252 253 public isJsOnlyProject() { 254 updateProjectIfDirty(this); 255 return hasOneOrMoreJsAndNoTsFiles(this); 256 } 257 258 public static resolveModule(moduleName: string, initialDir: string, host: ServerHost, log: (message: string) => void, logErrors?: (message: string) => void): {} | undefined { 259 const resolvedPath = normalizeSlashes(host.resolvePath(combinePaths(initialDir, "node_modules"))); 260 log(`Loading ${moduleName} from ${initialDir} (resolved to ${resolvedPath})`); 261 const result = host.require!(resolvedPath, moduleName); // TODO: GH#18217 262 if (result.error) { 263 const err = result.error.stack || result.error.message || JSON.stringify(result.error); 264 (logErrors || log)(`Failed to load module '${moduleName}' from ${resolvedPath}: ${err}`); 265 return undefined; 266 } 267 return result.module; 268 } 269 270 /*@internal*/ 271 public static async importServicePluginAsync(moduleName: string, initialDir: string, host: ServerHost, log: (message: string) => void, logErrors?: (message: string) => void): Promise<{} | undefined> { 272 Debug.assertIsDefined(host.importPlugin); 273 const resolvedPath = combinePaths(initialDir, "node_modules"); 274 log(`Dynamically importing ${moduleName} from ${initialDir} (resolved to ${resolvedPath})`); 275 let result: ModuleImportResult; 276 try { 277 result = await host.importPlugin(resolvedPath, moduleName); 278 } 279 catch (e) { 280 result = { module: undefined, error: e }; 281 } 282 if (result.error) { 283 const err = result.error.stack || result.error.message || JSON.stringify(result.error); 284 (logErrors || log)(`Failed to dynamically import module '${moduleName}' from ${resolvedPath}: ${err}`); 285 return undefined; 286 } 287 return result.module; 288 } 289 290 /*@internal*/ 291 readonly currentDirectory: string; 292 293 /*@internal*/ 294 public directoryStructureHost: DirectoryStructureHost; 295 296 /*@internal*/ 297 public readonly getCanonicalFileName: GetCanonicalFileName; 298 299 /*@internal*/ 300 private exportMapCache: ExportInfoMap | undefined; 301 /*@internal*/ 302 private changedFilesForExportMapCache: Set<Path> | undefined; 303 /*@internal*/ 304 private moduleSpecifierCache = createModuleSpecifierCache(this); 305 /*@internal*/ 306 private symlinks: SymlinkCache | undefined; 307 /*@internal*/ 308 autoImportProviderHost: AutoImportProviderProject | false | undefined; 309 /*@internal*/ 310 protected typeAcquisition: TypeAcquisition | undefined; 311 312 /*@internal*/ 313 constructor( 314 /*@internal*/ readonly projectName: string, 315 readonly projectKind: ProjectKind, 316 readonly projectService: ProjectService, 317 private documentRegistry: DocumentRegistry, 318 hasExplicitListOfFiles: boolean, 319 lastFileExceededProgramSize: string | undefined, 320 private compilerOptions: CompilerOptions, 321 public compileOnSaveEnabled: boolean, 322 protected watchOptions: WatchOptions | undefined, 323 directoryStructureHost: DirectoryStructureHost, 324 currentDirectory: string | undefined, 325 ) { 326 this.directoryStructureHost = directoryStructureHost; 327 this.currentDirectory = this.projectService.getNormalizedAbsolutePath(currentDirectory || ""); 328 this.getCanonicalFileName = this.projectService.toCanonicalFileName; 329 330 if (this.projectService.host.getFileCheckedModuleInfo) { 331 this.getFileCheckedModuleInfo = this.projectService.host.getFileCheckedModuleInfo;; 332 } 333 if (this.projectService.host.getJsDocNodeCheckedConfig) { 334 this.getJsDocNodeCheckedConfig = this.projectService.host.getJsDocNodeCheckedConfig; 335 } 336 if (this.projectService.host.getJsDocNodeConditionCheckedResult) { 337 this.getJsDocNodeConditionCheckedResult = this.projectService.host.getJsDocNodeConditionCheckedResult; 338 } 339 340 this.cancellationToken = new ThrottledCancellationToken(this.projectService.cancellationToken, this.projectService.throttleWaitMilliseconds); 341 if (!this.compilerOptions) { 342 this.compilerOptions = getDefaultCompilerOptions(); 343 this.compilerOptions.allowNonTsExtensions = true; 344 this.compilerOptions.allowJs = true; 345 } 346 else if (hasExplicitListOfFiles || getAllowJSCompilerOption(this.compilerOptions) || this.projectService.hasDeferredExtension()) { 347 // If files are listed explicitly or allowJs is specified, allow all extensions 348 this.compilerOptions.allowNonTsExtensions = true; 349 } 350 351 switch (projectService.serverMode) { 352 case LanguageServiceMode.Semantic: 353 this.languageServiceEnabled = true; 354 break; 355 case LanguageServiceMode.PartialSemantic: 356 this.languageServiceEnabled = true; 357 this.compilerOptions.noResolve = true; 358 this.compilerOptions.types = []; 359 break; 360 case LanguageServiceMode.Syntactic: 361 this.languageServiceEnabled = false; 362 this.compilerOptions.noResolve = true; 363 this.compilerOptions.types = []; 364 break; 365 default: 366 Debug.assertNever(projectService.serverMode); 367 } 368 369 this.setInternalCompilerOptionsForEmittingJsFiles(); 370 const host = this.projectService.host; 371 if (this.projectService.logger.loggingEnabled()) { 372 this.trace = s => this.writeLog(s); 373 } 374 else if (host.trace) { 375 this.trace = s => host.trace!(s); 376 } 377 this.realpath = maybeBind(host, host.realpath); 378 379 // Use the current directory as resolution root only if the project created using current directory string 380 this.resolutionCache = createResolutionCache( 381 this, 382 currentDirectory && this.currentDirectory, 383 /*logChangesWhenResolvingModule*/ true 384 ); 385 this.languageService = createLanguageService(this, this.documentRegistry, this.projectService.serverMode); 386 if (lastFileExceededProgramSize) { 387 this.disableLanguageService(lastFileExceededProgramSize); 388 } 389 this.markAsDirty(); 390 if (projectKind !== ProjectKind.AutoImportProvider) { 391 this.projectService.pendingEnsureProjectForOpenFiles = true; 392 } 393 } 394 395 getFileCheckedModuleInfo?(containFilePath: string): FileCheckModuleInfo { 396 Debug.log(containFilePath); 397 return { 398 fileNeedCheck: false, 399 checkPayload: undefined, 400 currentFileName: "", 401 } 402 } 403 404 getJsDocNodeCheckedConfig(jsDocFileCheckedInfo: FileCheckModuleInfo, sourceFilePath: string): JsDocNodeCheckConfig { 405 Debug.log(jsDocFileCheckedInfo.fileNeedCheck.toString()); 406 Debug.log(sourceFilePath); 407 return { 408 nodeNeedCheck: false, 409 checkConfig: [] 410 }; 411 } 412 413 getJsDocNodeConditionCheckedResult(jsDocFileCheckedInfo: FileCheckModuleInfo, jsDocs: JsDocTagInfo[]):ConditionCheckResult { 414 Debug.log(jsDocFileCheckedInfo.checkPayload); 415 Debug.log(jsDocs.toString()); 416 return { 417 valid: true, 418 message: "", 419 type: DiagnosticCategory.Warning 420 }; 421 } 422 423 getTagNameNeededCheckByFile(filePath: string): TagCheckParam { 424 Debug.log(filePath); 425 return { 426 needCheck: false, 427 checkConfig: [] 428 }; 429 } 430 431 getExpressionCheckedResultsByFile?(filePath: string, jsDocs: JSDoc[]): ConditionCheckResult { 432 Debug.log(filePath); 433 Debug.log(jsDocs.toString()); 434 return { 435 valid: true, 436 }; 437 } 438 439 isKnownTypesPackageName(name: string): boolean { 440 return this.typingsCache.isKnownTypesPackageName(name); 441 } 442 installPackage(options: InstallPackageOptions): Promise<ApplyCodeActionCommandResult> { 443 return this.typingsCache.installPackage({ ...options, projectName: this.projectName, projectRootPath: this.toPath(this.currentDirectory) }); 444 } 445 446 /*@internal*/ 447 getGlobalTypingsCacheLocation() { 448 return this.getGlobalCache(); 449 } 450 451 private get typingsCache(): TypingsCache { 452 return this.projectService.typingsCache; 453 } 454 455 /*@internal*/ 456 getSymlinkCache(): SymlinkCache { 457 if (!this.symlinks) { 458 this.symlinks = createSymlinkCache(this.getCurrentDirectory(), this.getCanonicalFileName, isOhpm(this.compilerOptions.packageManagerType)); 459 } 460 if (this.program && !this.symlinks.hasProcessedResolutions()) { 461 this.symlinks.setSymlinksFromResolutions( 462 this.program.getSourceFiles(), 463 this.program.getResolvedTypeReferenceDirectives()); 464 } 465 return this.symlinks; 466 } 467 468 // Method of LanguageServiceHost 469 getCompilationSettings() { 470 return this.compilerOptions; 471 } 472 473 // Method to support public API 474 getCompilerOptions() { 475 return this.getCompilationSettings(); 476 } 477 478 getNewLine() { 479 return this.projectService.host.newLine; 480 } 481 482 getProjectVersion() { 483 return this.projectStateVersion.toString(); 484 } 485 486 getProjectReferences(): readonly ProjectReference[] | undefined { 487 return undefined; 488 } 489 490 getScriptFileNames() { 491 if (!this.rootFiles) { 492 return ts.emptyArray; 493 } 494 495 let result: string[] | undefined; 496 this.rootFilesMap.forEach(value => { 497 if (this.languageServiceEnabled || (value.info && value.info.isScriptOpen())) { 498 // if language service is disabled - process only files that are open 499 (result || (result = [])).push(value.fileName); 500 } 501 }); 502 503 return addRange(result, this.typingFiles) || ts.emptyArray; 504 } 505 506 private getOrCreateScriptInfoAndAttachToProject(fileName: string) { 507 const scriptInfo = this.projectService.getOrCreateScriptInfoNotOpenedByClient(fileName, this.currentDirectory, this.directoryStructureHost); 508 if (scriptInfo) { 509 const existingValue = this.rootFilesMap.get(scriptInfo.path); 510 if (existingValue && existingValue.info !== scriptInfo) { 511 // This was missing path earlier but now the file exists. Update the root 512 this.rootFiles.push(scriptInfo); 513 existingValue.info = scriptInfo; 514 } 515 scriptInfo.attachToProject(this); 516 } 517 return scriptInfo; 518 } 519 520 getScriptKind(fileName: string) { 521 const info = this.getOrCreateScriptInfoAndAttachToProject(fileName); 522 return (info && info.scriptKind)!; // TODO: GH#18217 523 } 524 525 getScriptVersion(filename: string) { 526 // Don't attach to the project if version is asked 527 528 const info = this.projectService.getOrCreateScriptInfoNotOpenedByClient(filename, this.currentDirectory, this.directoryStructureHost); 529 return (info && info.getLatestVersion())!; // TODO: GH#18217 530 } 531 532 getScriptSnapshot(filename: string): IScriptSnapshot | undefined { 533 const scriptInfo = this.getOrCreateScriptInfoAndAttachToProject(filename); 534 if (scriptInfo) { 535 return scriptInfo.getSnapshot(); 536 } 537 } 538 539 getCancellationToken(): HostCancellationToken { 540 return this.cancellationToken; 541 } 542 543 getCurrentDirectory(): string { 544 return this.currentDirectory; 545 } 546 547 getDefaultLibFileName() { 548 const nodeModuleBinDir = getDirectoryPath(normalizePath(this.projectService.getExecutingFilePath())); 549 return combinePaths(nodeModuleBinDir, getDefaultLibFileName(this.compilerOptions)); 550 } 551 552 useCaseSensitiveFileNames() { 553 return this.projectService.host.useCaseSensitiveFileNames; 554 } 555 556 readDirectory(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[] { 557 return this.directoryStructureHost.readDirectory!(path, extensions, exclude, include, depth); 558 } 559 560 readFile(fileName: string): string | undefined { 561 return this.projectService.host.readFile(fileName); 562 } 563 564 writeFile(fileName: string, content: string): void { 565 return this.projectService.host.writeFile(fileName, content); 566 } 567 568 fileExists(file: string): boolean { 569 // As an optimization, don't hit the disks for files we already know don't exist 570 // (because we're watching for their creation). 571 const path = this.toPath(file); 572 return !this.isWatchedMissingFile(path) && this.directoryStructureHost.fileExists(file); 573 } 574 575 resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames?: string[], redirectedReference?: ResolvedProjectReference, _options?: CompilerOptions, containingSourceFile?: SourceFile): (ResolvedModuleFull | undefined)[] { 576 return this.resolutionCache.resolveModuleNames(moduleNames, containingFile, reusedNames, redirectedReference, containingSourceFile); 577 } 578 579 getModuleResolutionCache(): ModuleResolutionCache | undefined { 580 return this.resolutionCache.getModuleResolutionCache(); 581 } 582 583 getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations | undefined { 584 return this.resolutionCache.getResolvedModuleWithFailedLookupLocationsFromCache(moduleName, containingFile, resolutionMode); 585 } 586 587 resolveTypeReferenceDirectives(typeDirectiveNames: string[] | FileReference[], containingFile: string, redirectedReference?: ResolvedProjectReference, _options?: CompilerOptions, containingFileMode?: SourceFile["impliedNodeFormat"] | undefined): (ResolvedTypeReferenceDirective | undefined)[] { 588 return this.resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile, redirectedReference, containingFileMode); 589 } 590 591 directoryExists(path: string): boolean { 592 return this.directoryStructureHost.directoryExists!(path); // TODO: GH#18217 593 } 594 595 getDirectories(path: string): string[] { 596 return this.directoryStructureHost.getDirectories!(path); // TODO: GH#18217 597 } 598 599 /*@internal*/ 600 getCachedDirectoryStructureHost(): CachedDirectoryStructureHost { 601 return undefined!; // TODO: GH#18217 602 } 603 604 /*@internal*/ 605 toPath(fileName: string) { 606 return toPath(fileName, this.currentDirectory, this.projectService.toCanonicalFileName); 607 } 608 609 /*@internal*/ 610 watchDirectoryOfFailedLookupLocation(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags) { 611 return this.projectService.watchFactory.watchDirectory( 612 directory, 613 cb, 614 flags, 615 this.projectService.getWatchOptions(this), 616 WatchType.FailedLookupLocations, 617 this 618 ); 619 } 620 621 /*@internal*/ 622 watchAffectingFileLocation(file: string, cb: FileWatcherCallback) { 623 return this.projectService.watchFactory.watchFile( 624 file, 625 cb, 626 PollingInterval.High, 627 this.projectService.getWatchOptions(this), 628 WatchType.AffectingFileLocation, 629 this 630 ); 631 } 632 633 /*@internal*/ 634 clearInvalidateResolutionOfFailedLookupTimer() { 635 return this.projectService.throttledOperations.cancel(`${this.getProjectName()}FailedLookupInvalidation`); 636 } 637 638 /*@internal*/ 639 scheduleInvalidateResolutionsOfFailedLookupLocations() { 640 this.projectService.throttledOperations.schedule(`${this.getProjectName()}FailedLookupInvalidation`, /*delay*/ 1000, () => { 641 if (this.resolutionCache.invalidateResolutionsOfFailedLookupLocations()) { 642 this.projectService.delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(this); 643 } 644 }); 645 } 646 647 /*@internal*/ 648 invalidateResolutionsOfFailedLookupLocations() { 649 if (this.clearInvalidateResolutionOfFailedLookupTimer() && 650 this.resolutionCache.invalidateResolutionsOfFailedLookupLocations()) { 651 this.markAsDirty(); 652 this.projectService.delayEnsureProjectForOpenFiles(); 653 } 654 } 655 656 /*@internal*/ 657 onInvalidatedResolution() { 658 this.projectService.delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(this); 659 } 660 661 /*@internal*/ 662 watchTypeRootsDirectory(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags) { 663 return this.projectService.watchFactory.watchDirectory( 664 directory, 665 cb, 666 flags, 667 this.projectService.getWatchOptions(this), 668 WatchType.TypeRoots, 669 this 670 ); 671 } 672 673 /*@internal*/ 674 hasChangedAutomaticTypeDirectiveNames() { 675 return this.resolutionCache.hasChangedAutomaticTypeDirectiveNames(); 676 } 677 678 /*@internal*/ 679 onChangedAutomaticTypeDirectiveNames() { 680 this.projectService.delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(this); 681 } 682 683 /*@internal*/ 684 getGlobalCache() { 685 return this.getTypeAcquisition().enable ? this.projectService.typingsInstaller.globalTypingsCacheLocation : undefined; 686 } 687 688 /*@internal*/ 689 globalCacheResolutionModuleName = JsTyping.nonRelativeModuleNameForTypingCache; 690 691 /*@internal*/ 692 fileIsOpen(filePath: Path) { 693 return this.projectService.openFiles.has(filePath); 694 } 695 696 /*@internal*/ 697 writeLog(s: string) { 698 this.projectService.logger.info(s); 699 } 700 701 log(s: string) { 702 this.writeLog(s); 703 } 704 705 error(s: string) { 706 this.projectService.logger.msg(s, Msg.Err); 707 } 708 709 private setInternalCompilerOptionsForEmittingJsFiles() { 710 if (this.projectKind === ProjectKind.Inferred || this.projectKind === ProjectKind.External) { 711 this.compilerOptions.noEmitForJsFiles = true; 712 } 713 } 714 715 /** 716 * Get the errors that dont have any file name associated 717 */ 718 getGlobalProjectErrors(): readonly Diagnostic[] { 719 return filter(this.projectErrors, diagnostic => !diagnostic.file) || emptyArray; 720 } 721 722 /** 723 * Get all the project errors 724 */ 725 getAllProjectErrors(): readonly Diagnostic[] { 726 return this.projectErrors || emptyArray; 727 } 728 729 setProjectErrors(projectErrors: Diagnostic[] | undefined) { 730 this.projectErrors = projectErrors; 731 } 732 733 getLanguageService(ensureSynchronized = true): LanguageService { 734 if (ensureSynchronized) { 735 updateProjectIfDirty(this); 736 } 737 return this.languageService; 738 } 739 740 /** @internal */ 741 getSourceMapper(): SourceMapper { 742 return this.getLanguageService().getSourceMapper(); 743 } 744 745 /** @internal */ 746 clearSourceMapperCache() { 747 this.languageService.clearSourceMapperCache(); 748 } 749 750 /*@internal*/ 751 getDocumentPositionMapper(generatedFileName: string, sourceFileName?: string): DocumentPositionMapper | undefined { 752 return this.projectService.getDocumentPositionMapper(this, generatedFileName, sourceFileName); 753 } 754 755 /*@internal*/ 756 getSourceFileLike(fileName: string) { 757 return this.projectService.getSourceFileLike(fileName, this); 758 } 759 760 /*@internal*/ 761 shouldEmitFile(scriptInfo: ScriptInfo | undefined) { 762 return scriptInfo && 763 !scriptInfo.isDynamicOrHasMixedContent() && 764 !this.program!.isSourceOfProjectReferenceRedirect(scriptInfo.path); 765 } 766 767 getCompileOnSaveAffectedFileList(scriptInfo: ScriptInfo): string[] { 768 if (!this.languageServiceEnabled) { 769 return []; 770 } 771 updateProjectIfDirty(this); 772 this.builderState = BuilderState.create(this.program!, this.projectService.toCanonicalFileName, this.builderState, /*disableUseFileVersionAsSignature*/ true); 773 return mapDefined( 774 BuilderState.getFilesAffectedBy( 775 this.builderState, 776 this.program!, 777 scriptInfo.path, 778 this.cancellationToken, 779 maybeBind(this.projectService.host, this.projectService.host.createHash), 780 this.getCanonicalFileName, 781 ), 782 sourceFile => this.shouldEmitFile(this.projectService.getScriptInfoForPath(sourceFile.path)) ? sourceFile.fileName : undefined 783 ); 784 } 785 786 /** 787 * Returns true if emit was conducted 788 */ 789 emitFile(scriptInfo: ScriptInfo, writeFile: (path: string, data: string, writeByteOrderMark?: boolean) => void): EmitResult { 790 if (!this.languageServiceEnabled || !this.shouldEmitFile(scriptInfo)) { 791 return { emitSkipped: true, diagnostics: emptyArray }; 792 } 793 const { emitSkipped, diagnostics, outputFiles } = this.getLanguageService().getEmitOutput(scriptInfo.fileName); 794 if (!emitSkipped) { 795 for (const outputFile of outputFiles) { 796 const outputFileAbsoluteFileName = getNormalizedAbsolutePath(outputFile.name, this.currentDirectory); 797 writeFile(outputFileAbsoluteFileName, outputFile.text, outputFile.writeByteOrderMark); 798 } 799 800 // Update the signature 801 if (this.builderState && getEmitDeclarations(this.compilerOptions)) { 802 const dtsFiles = outputFiles.filter(f => isDeclarationFileName(f.name)); 803 if (dtsFiles.length === 1) { 804 const sourceFile = this.program!.getSourceFile(scriptInfo.fileName)!; 805 const signature = this.projectService.host.createHash ? 806 this.projectService.host.createHash(dtsFiles[0].text) : 807 generateDjb2Hash(dtsFiles[0].text); 808 BuilderState.updateSignatureOfFile(this.builderState, signature, sourceFile.resolvedPath); 809 } 810 } 811 } 812 813 return { emitSkipped, diagnostics }; 814 } 815 816 enableLanguageService() { 817 if (this.languageServiceEnabled || this.projectService.serverMode === LanguageServiceMode.Syntactic) { 818 return; 819 } 820 this.languageServiceEnabled = true; 821 this.lastFileExceededProgramSize = undefined; 822 this.projectService.onUpdateLanguageServiceStateForProject(this, /*languageServiceEnabled*/ true); 823 } 824 825 disableLanguageService(lastFileExceededProgramSize?: string) { 826 if (!this.languageServiceEnabled) { 827 return; 828 } 829 Debug.assert(this.projectService.serverMode !== LanguageServiceMode.Syntactic); 830 this.languageService.cleanupSemanticCache(); 831 this.languageServiceEnabled = false; 832 this.lastFileExceededProgramSize = lastFileExceededProgramSize; 833 this.builderState = undefined; 834 if (this.autoImportProviderHost) { 835 this.autoImportProviderHost.close(); 836 } 837 this.autoImportProviderHost = undefined; 838 this.resolutionCache.closeTypeRootsWatch(); 839 this.clearGeneratedFileWatch(); 840 this.projectService.onUpdateLanguageServiceStateForProject(this, /*languageServiceEnabled*/ false); 841 } 842 843 getProjectName() { 844 return this.projectName; 845 } 846 847 protected removeLocalTypingsFromTypeAcquisition(newTypeAcquisition: TypeAcquisition): TypeAcquisition { 848 if (!newTypeAcquisition || !newTypeAcquisition.include) { 849 // Nothing to filter out, so just return as-is 850 return newTypeAcquisition; 851 } 852 return { ...newTypeAcquisition, include: this.removeExistingTypings(newTypeAcquisition.include) }; 853 } 854 855 getExternalFiles(): SortedReadonlyArray<string> { 856 return sort(flatMap(this.plugins, plugin => { 857 if (typeof plugin.module.getExternalFiles !== "function") return; 858 try { 859 return plugin.module.getExternalFiles(this); 860 } 861 catch (e) { 862 this.projectService.logger.info(`A plugin threw an exception in getExternalFiles: ${e}`); 863 if (e.stack) { 864 this.projectService.logger.info(e.stack); 865 } 866 } 867 })); 868 } 869 870 getSourceFile(path: Path) { 871 if (!this.program) { 872 return undefined; 873 } 874 return this.program.getSourceFileByPath(path); 875 } 876 877 /* @internal */ 878 getSourceFileOrConfigFile(path: Path): SourceFile | undefined { 879 const options = this.program!.getCompilerOptions(); 880 return path === options.configFilePath ? options.configFile : this.getSourceFile(path); 881 } 882 883 close() { 884 if (this.program) { 885 // if we have a program - release all files that are enlisted in program but arent root 886 // The releasing of the roots happens later 887 // The project could have pending update remaining and hence the info could be in the files but not in program graph 888 for (const f of this.program.getSourceFiles()) { 889 this.detachScriptInfoIfNotRoot(f.fileName); 890 } 891 this.program.forEachResolvedProjectReference(ref => 892 this.detachScriptInfoFromProject(ref.sourceFile.fileName)); 893 } 894 895 // Release external files 896 forEach(this.externalFiles, externalFile => this.detachScriptInfoIfNotRoot(externalFile)); 897 // Always remove root files from the project 898 for (const root of this.rootFiles) { 899 root.detachFromProject(this); 900 } 901 this.projectService.pendingEnsureProjectForOpenFiles = true; 902 903 this.rootFiles = undefined!; 904 this.rootFilesMap = undefined!; 905 this.externalFiles = undefined; 906 this.program = undefined; 907 this.builderState = undefined; 908 this.resolutionCache.clear(); 909 this.resolutionCache = undefined!; 910 this.cachedUnresolvedImportsPerFile = undefined!; 911 this.moduleSpecifierCache = undefined!; 912 this.directoryStructureHost = undefined!; 913 this.exportMapCache = undefined; 914 this.projectErrors = undefined; 915 this.plugins.length = 0; 916 917 // Clean up file watchers waiting for missing files 918 if (this.missingFilesMap) { 919 clearMap(this.missingFilesMap, closeFileWatcher); 920 this.missingFilesMap = undefined; 921 } 922 this.clearGeneratedFileWatch(); 923 this.clearInvalidateResolutionOfFailedLookupTimer(); 924 if (this.autoImportProviderHost) { 925 this.autoImportProviderHost.close(); 926 } 927 this.autoImportProviderHost = undefined; 928 if (this.noDtsResolutionProject) { 929 this.noDtsResolutionProject.close(); 930 } 931 this.noDtsResolutionProject = undefined; 932 933 // signal language service to release source files acquired from document registry 934 this.languageService.dispose(); 935 this.languageService = undefined!; 936 } 937 938 private detachScriptInfoIfNotRoot(uncheckedFilename: string) { 939 const info = this.projectService.getScriptInfo(uncheckedFilename); 940 // We might not find the script info in case its not associated with the project any more 941 // and project graph was not updated (eg delayed update graph in case of files changed/deleted on the disk) 942 if (info && !this.isRoot(info)) { 943 info.detachFromProject(this); 944 } 945 } 946 947 isClosed() { 948 return this.rootFiles === undefined; 949 } 950 951 hasRoots() { 952 return this.rootFiles && this.rootFiles.length > 0; 953 } 954 955 /*@internal*/ 956 isOrphan() { 957 return false; 958 } 959 960 getRootFiles() { 961 return this.rootFiles && this.rootFiles.map(info => info.fileName); 962 } 963 964 /*@internal*/ 965 getRootFilesMap() { 966 return this.rootFilesMap; 967 } 968 969 getRootScriptInfos() { 970 return this.rootFiles; 971 } 972 973 getScriptInfos(): ScriptInfo[] { 974 if (!this.languageServiceEnabled) { 975 // if language service is not enabled - return just root files 976 return this.rootFiles; 977 } 978 return map(this.program!.getSourceFiles(), sourceFile => { 979 const scriptInfo = this.projectService.getScriptInfoForPath(sourceFile.resolvedPath); 980 Debug.assert(!!scriptInfo, "getScriptInfo", () => `scriptInfo for a file '${sourceFile.fileName}' Path: '${sourceFile.path}' / '${sourceFile.resolvedPath}' is missing.`); 981 return scriptInfo; 982 }); 983 } 984 985 getExcludedFiles(): readonly NormalizedPath[] { 986 return emptyArray; 987 } 988 989 getFileNames(excludeFilesFromExternalLibraries?: boolean, excludeConfigFiles?: boolean) { 990 if (!this.program) { 991 return []; 992 } 993 994 if (!this.languageServiceEnabled) { 995 // if language service is disabled assume that all files in program are root files + default library 996 let rootFiles = this.getRootFiles(); 997 if (this.compilerOptions) { 998 const defaultLibrary = getDefaultLibFilePath(this.compilerOptions); 999 if (defaultLibrary) { 1000 (rootFiles || (rootFiles = [])).push(asNormalizedPath(defaultLibrary)); 1001 } 1002 } 1003 return rootFiles; 1004 } 1005 const result: NormalizedPath[] = []; 1006 for (const f of this.program.getSourceFiles()) { 1007 if (excludeFilesFromExternalLibraries && this.program.isSourceFileFromExternalLibrary(f)) { 1008 continue; 1009 } 1010 result.push(asNormalizedPath(f.fileName)); 1011 } 1012 if (!excludeConfigFiles) { 1013 const configFile = this.program.getCompilerOptions().configFile; 1014 if (configFile) { 1015 result.push(asNormalizedPath(configFile.fileName)); 1016 if (configFile.extendedSourceFiles) { 1017 for (const f of configFile.extendedSourceFiles) { 1018 result.push(asNormalizedPath(f)); 1019 } 1020 } 1021 } 1022 } 1023 return result; 1024 } 1025 1026 /* @internal */ 1027 getFileNamesWithRedirectInfo(includeProjectReferenceRedirectInfo: boolean) { 1028 return this.getFileNames().map((fileName): protocol.FileWithProjectReferenceRedirectInfo => ({ 1029 fileName, 1030 isSourceOfProjectReferenceRedirect: includeProjectReferenceRedirectInfo && this.isSourceOfProjectReferenceRedirect(fileName) 1031 })); 1032 } 1033 1034 hasConfigFile(configFilePath: NormalizedPath) { 1035 if (this.program && this.languageServiceEnabled) { 1036 const configFile = this.program.getCompilerOptions().configFile; 1037 if (configFile) { 1038 if (configFilePath === asNormalizedPath(configFile.fileName)) { 1039 return true; 1040 } 1041 if (configFile.extendedSourceFiles) { 1042 for (const f of configFile.extendedSourceFiles) { 1043 if (configFilePath === asNormalizedPath(f)) { 1044 return true; 1045 } 1046 } 1047 } 1048 } 1049 } 1050 return false; 1051 } 1052 1053 containsScriptInfo(info: ScriptInfo): boolean { 1054 if (this.isRoot(info)) return true; 1055 if (!this.program) return false; 1056 const file = this.program.getSourceFileByPath(info.path); 1057 return !!file && file.resolvedPath === info.path; 1058 } 1059 1060 containsFile(filename: NormalizedPath, requireOpen?: boolean): boolean { 1061 const info = this.projectService.getScriptInfoForNormalizedPath(filename); 1062 if (info && (info.isScriptOpen() || !requireOpen)) { 1063 return this.containsScriptInfo(info); 1064 } 1065 return false; 1066 } 1067 1068 isRoot(info: ScriptInfo) { 1069 return this.rootFilesMap && this.rootFilesMap.get(info.path)?.info === info; 1070 } 1071 1072 // add a root file to project 1073 addRoot(info: ScriptInfo, fileName?: NormalizedPath) { 1074 Debug.assert(!this.isRoot(info)); 1075 this.rootFiles.push(info); 1076 this.rootFilesMap.set(info.path, { fileName: fileName || info.fileName, info }); 1077 info.attachToProject(this); 1078 1079 this.markAsDirty(); 1080 } 1081 1082 // add a root file that doesnt exist on host 1083 addMissingFileRoot(fileName: NormalizedPath) { 1084 const path = this.projectService.toPath(fileName); 1085 this.rootFilesMap.set(path, { fileName }); 1086 this.markAsDirty(); 1087 } 1088 1089 removeFile(info: ScriptInfo, fileExists: boolean, detachFromProject: boolean) { 1090 if (this.isRoot(info)) { 1091 this.removeRoot(info); 1092 } 1093 if (fileExists) { 1094 // If file is present, just remove the resolutions for the file 1095 this.resolutionCache.removeResolutionsOfFile(info.path); 1096 } 1097 else { 1098 this.resolutionCache.invalidateResolutionOfFile(info.path); 1099 } 1100 this.cachedUnresolvedImportsPerFile.delete(info.path); 1101 1102 if (detachFromProject) { 1103 info.detachFromProject(this); 1104 } 1105 1106 this.markAsDirty(); 1107 } 1108 1109 registerFileUpdate(fileName: string) { 1110 (this.updatedFileNames || (this.updatedFileNames = new Set<string>())).add(fileName); 1111 } 1112 1113 /*@internal*/ 1114 markFileAsDirty(changedFile: Path) { 1115 this.markAsDirty(); 1116 if (this.exportMapCache && !this.exportMapCache.isEmpty()) { 1117 (this.changedFilesForExportMapCache ||= new Set()).add(changedFile); 1118 } 1119 } 1120 1121 markAsDirty() { 1122 if (!this.dirty) { 1123 this.projectStateVersion++; 1124 this.dirty = true; 1125 } 1126 } 1127 1128 /*@internal*/ 1129 onAutoImportProviderSettingsChanged() { 1130 if (this.autoImportProviderHost === false) { 1131 this.autoImportProviderHost = undefined; 1132 } 1133 else { 1134 this.autoImportProviderHost?.markAsDirty(); 1135 } 1136 } 1137 1138 /*@internal*/ 1139 onPackageJsonChange(packageJsonPath: Path) { 1140 if (this.packageJsonsForAutoImport?.has(packageJsonPath)) { 1141 this.moduleSpecifierCache.clear(); 1142 if (this.autoImportProviderHost) { 1143 this.autoImportProviderHost.markAsDirty(); 1144 } 1145 } 1146 } 1147 1148 /* @internal */ 1149 onFileAddedOrRemoved(isSymlink: boolean | undefined) { 1150 this.hasAddedorRemovedFiles = true; 1151 if (isSymlink) { 1152 this.hasAddedOrRemovedSymlinks = true; 1153 } 1154 } 1155 1156 /* @internal */ 1157 onDiscoveredSymlink() { 1158 this.hasAddedOrRemovedSymlinks = true; 1159 } 1160 1161 /** 1162 * Updates set of files that contribute to this project 1163 * @returns: true if set of files in the project stays the same and false - otherwise. 1164 */ 1165 updateGraph(): boolean { 1166 tracing?.push(tracing.Phase.Session, "updateGraph", { name: this.projectName, kind: ProjectKind[this.projectKind] }); 1167 perfLogger.logStartUpdateGraph(); 1168 this.resolutionCache.startRecordingFilesWithChangedResolutions(); 1169 1170 const hasNewProgram = this.updateGraphWorker(); 1171 const hasAddedorRemovedFiles = this.hasAddedorRemovedFiles; 1172 this.hasAddedorRemovedFiles = false; 1173 this.hasAddedOrRemovedSymlinks = false; 1174 1175 const changedFiles: readonly Path[] = this.resolutionCache.finishRecordingFilesWithChangedResolutions() || emptyArray; 1176 1177 for (const file of changedFiles) { 1178 // delete cached information for changed files 1179 this.cachedUnresolvedImportsPerFile.delete(file); 1180 } 1181 1182 // update builder only if language service is enabled 1183 // otherwise tell it to drop its internal state 1184 if (this.languageServiceEnabled && this.projectService.serverMode === LanguageServiceMode.Semantic) { 1185 // 1. no changes in structure, no changes in unresolved imports - do nothing 1186 // 2. no changes in structure, unresolved imports were changed - collect unresolved imports for all files 1187 // (can reuse cached imports for files that were not changed) 1188 // 3. new files were added/removed, but compilation settings stays the same - collect unresolved imports for all new/modified files 1189 // (can reuse cached imports for files that were not changed) 1190 // 4. compilation settings were changed in the way that might affect module resolution - drop all caches and collect all data from the scratch 1191 if (hasNewProgram || changedFiles.length) { 1192 this.lastCachedUnresolvedImportsList = getUnresolvedImports(this.program!, this.cachedUnresolvedImportsPerFile); 1193 } 1194 1195 this.projectService.typingsCache.enqueueInstallTypingsForProject(this, this.lastCachedUnresolvedImportsList, hasAddedorRemovedFiles); 1196 } 1197 else { 1198 this.lastCachedUnresolvedImportsList = undefined; 1199 } 1200 1201 const isFirstProgramLoad = this.projectProgramVersion === 0 && hasNewProgram; 1202 if (hasNewProgram) { 1203 this.projectProgramVersion++; 1204 } 1205 if (hasAddedorRemovedFiles) { 1206 if (!this.autoImportProviderHost) this.autoImportProviderHost = undefined; 1207 this.autoImportProviderHost?.markAsDirty(); 1208 } 1209 if (isFirstProgramLoad) { 1210 // Preload auto import provider so it's not created during completions request 1211 this.getPackageJsonAutoImportProvider(); 1212 } 1213 perfLogger.logStopUpdateGraph(); 1214 tracing?.pop(); 1215 return !hasNewProgram; 1216 } 1217 1218 /*@internal*/ 1219 updateTypingFiles(typingFiles: SortedReadonlyArray<string>) { 1220 if (enumerateInsertsAndDeletes<string, string>(typingFiles, this.typingFiles, getStringComparer(!this.useCaseSensitiveFileNames()), 1221 /*inserted*/ noop, 1222 removed => this.detachScriptInfoFromProject(removed) 1223 )) { 1224 // If typing files changed, then only schedule project update 1225 this.typingFiles = typingFiles; 1226 // Invalidate files with unresolved imports 1227 this.resolutionCache.setFilesWithInvalidatedNonRelativeUnresolvedImports(this.cachedUnresolvedImportsPerFile); 1228 this.projectService.delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(this); 1229 } 1230 } 1231 1232 /* @internal */ 1233 getCurrentProgram(): Program | undefined { 1234 return this.program; 1235 } 1236 1237 protected removeExistingTypings(include: string[]): string[] { 1238 const existing = getAutomaticTypeDirectiveNames(this.getCompilerOptions(), this.directoryStructureHost); 1239 return include.filter(i => existing.indexOf(i) < 0); 1240 } 1241 1242 private updateGraphWorker() { 1243 const oldProgram = this.languageService.getCurrentProgram(); 1244 Debug.assert(!this.isClosed(), "Called update graph worker of closed project"); 1245 this.writeLog(`Starting updateGraphWorker: Project: ${this.getProjectName()}`); 1246 const start = timestamp(); 1247 this.hasInvalidatedResolutions = this.resolutionCache.createHasInvalidatedResolutions(returnFalse); 1248 this.resolutionCache.startCachingPerDirectoryResolution(); 1249 this.program = this.languageService.getProgram(); // TODO: GH#18217 1250 this.dirty = false; 1251 tracing?.push(tracing.Phase.Session, "finishCachingPerDirectoryResolution"); 1252 this.resolutionCache.finishCachingPerDirectoryResolution(this.program, oldProgram); 1253 tracing?.pop(); 1254 1255 Debug.assert(oldProgram === undefined || this.program !== undefined); 1256 1257 // bump up the version if 1258 // - oldProgram is not set - this is a first time updateGraph is called 1259 // - newProgram is different from the old program and structure of the old program was not reused. 1260 let hasNewProgram = false; 1261 if (this.program && (!oldProgram || (this.program !== oldProgram && this.program.structureIsReused !== StructureIsReused.Completely))) { 1262 hasNewProgram = true; 1263 if (oldProgram) { 1264 for (const f of oldProgram.getSourceFiles()) { 1265 const newFile = this.program.getSourceFileByPath(f.resolvedPath); 1266 if (!newFile || (f.resolvedPath === f.path && newFile.resolvedPath !== f.path)) { 1267 // new program does not contain this file - detach it from the project 1268 // - remove resolutions only if the new program doesnt contain source file by the path (not resolvedPath since path is used for resolution) 1269 this.detachScriptInfoFromProject(f.fileName, !!this.program.getSourceFileByPath(f.path)); 1270 } 1271 } 1272 1273 oldProgram.forEachResolvedProjectReference(resolvedProjectReference => { 1274 if (!this.program!.getResolvedProjectReferenceByPath(resolvedProjectReference.sourceFile.path)) { 1275 this.detachScriptInfoFromProject(resolvedProjectReference.sourceFile.fileName); 1276 } 1277 }); 1278 } 1279 1280 // Update the missing file paths watcher 1281 updateMissingFilePathsWatch( 1282 this.program, 1283 this.missingFilesMap || (this.missingFilesMap = new Map()), 1284 // Watch the missing files 1285 missingFilePath => this.addMissingFileWatcher(missingFilePath) 1286 ); 1287 1288 if (this.generatedFilesMap) { 1289 const outPath = outFile(this.compilerOptions); 1290 if (isGeneratedFileWatcher(this.generatedFilesMap)) { 1291 // --out 1292 if (!outPath || !this.isValidGeneratedFileWatcher( 1293 removeFileExtension(outPath) + Extension.Dts, 1294 this.generatedFilesMap, 1295 )) { 1296 this.clearGeneratedFileWatch(); 1297 } 1298 } 1299 else { 1300 // MultiFile 1301 if (outPath) { 1302 this.clearGeneratedFileWatch(); 1303 } 1304 else { 1305 this.generatedFilesMap.forEach((watcher, source) => { 1306 const sourceFile = this.program!.getSourceFileByPath(source); 1307 if (!sourceFile || 1308 sourceFile.resolvedPath !== source || 1309 !this.isValidGeneratedFileWatcher( 1310 getDeclarationEmitOutputFilePathWorker(sourceFile.fileName, this.compilerOptions, this.currentDirectory, this.program!.getCommonSourceDirectory(), this.getCanonicalFileName), 1311 watcher 1312 )) { 1313 closeFileWatcherOf(watcher); 1314 (this.generatedFilesMap as ESMap<string, GeneratedFileWatcher>).delete(source); 1315 } 1316 }); 1317 } 1318 } 1319 } 1320 1321 // Watch the type locations that would be added to program as part of automatic type resolutions 1322 if (this.languageServiceEnabled && this.projectService.serverMode === LanguageServiceMode.Semantic) { 1323 this.resolutionCache.updateTypeRootsWatch(); 1324 } 1325 } 1326 1327 if (this.exportMapCache && !this.exportMapCache.isEmpty()) { 1328 this.exportMapCache.releaseSymbols(); 1329 if (this.hasAddedorRemovedFiles || oldProgram && !this.program!.structureIsReused) { 1330 this.exportMapCache.clear(); 1331 } 1332 else if (this.changedFilesForExportMapCache && oldProgram && this.program) { 1333 forEachKey(this.changedFilesForExportMapCache, fileName => { 1334 const oldSourceFile = oldProgram.getSourceFileByPath(fileName); 1335 const sourceFile = this.program!.getSourceFileByPath(fileName); 1336 if (!oldSourceFile || !sourceFile) { 1337 this.exportMapCache!.clear(); 1338 return true; 1339 } 1340 return this.exportMapCache!.onFileChanged(oldSourceFile, sourceFile, !!this.getTypeAcquisition().enable); 1341 }); 1342 } 1343 } 1344 if (this.changedFilesForExportMapCache) { 1345 this.changedFilesForExportMapCache.clear(); 1346 } 1347 1348 if (this.hasAddedOrRemovedSymlinks || this.program && !this.program.structureIsReused && this.getCompilerOptions().preserveSymlinks) { 1349 // With --preserveSymlinks, we may not determine that a file is a symlink, so we never set `hasAddedOrRemovedSymlinks` 1350 this.symlinks = undefined; 1351 this.moduleSpecifierCache.clear(); 1352 } 1353 1354 const oldExternalFiles = this.externalFiles || emptyArray as SortedReadonlyArray<string>; 1355 this.externalFiles = this.getExternalFiles(); 1356 enumerateInsertsAndDeletes<string, string>(this.externalFiles, oldExternalFiles, getStringComparer(!this.useCaseSensitiveFileNames()), 1357 // Ensure a ScriptInfo is created for new external files. This is performed indirectly 1358 // by the host for files in the program when the program is retrieved above but 1359 // the program doesn't contain external files so this must be done explicitly. 1360 inserted => { 1361 const scriptInfo = this.projectService.getOrCreateScriptInfoNotOpenedByClient(inserted, this.currentDirectory, this.directoryStructureHost); 1362 scriptInfo?.attachToProject(this); 1363 }, 1364 removed => this.detachScriptInfoFromProject(removed) 1365 ); 1366 const elapsed = timestamp() - start; 1367 this.sendPerformanceEvent("UpdateGraph", elapsed); 1368 this.writeLog(`Finishing updateGraphWorker: Project: ${this.getProjectName()} Version: ${this.getProjectVersion()} structureChanged: ${hasNewProgram}${this.program ? ` structureIsReused:: ${(ts as any).StructureIsReused[this.program.structureIsReused]}` : ""} Elapsed: ${elapsed}ms`); 1369 if (this.hasAddedorRemovedFiles) { 1370 this.print(/*writeProjectFileNames*/ true); 1371 } 1372 else if (this.program !== oldProgram) { 1373 this.writeLog(`Different program with same set of files`); 1374 } 1375 return hasNewProgram; 1376 } 1377 1378 /* @internal */ 1379 sendPerformanceEvent(kind: PerformanceEvent["kind"], durationMs: number) { 1380 this.projectService.sendPerformanceEvent(kind, durationMs); 1381 } 1382 1383 private detachScriptInfoFromProject(uncheckedFileName: string, noRemoveResolution?: boolean) { 1384 const scriptInfoToDetach = this.projectService.getScriptInfo(uncheckedFileName); 1385 if (scriptInfoToDetach) { 1386 scriptInfoToDetach.detachFromProject(this); 1387 if (!noRemoveResolution) { 1388 this.resolutionCache.removeResolutionsOfFile(scriptInfoToDetach.path); 1389 } 1390 } 1391 } 1392 1393 private addMissingFileWatcher(missingFilePath: Path) { 1394 if (isConfiguredProject(this)) { 1395 // If this file is referenced config file, we are already watching it, no need to watch again 1396 const configFileExistenceInfo = this.projectService.configFileExistenceInfoCache.get(missingFilePath as string as NormalizedPath); 1397 if (configFileExistenceInfo?.config?.projects.has(this.canonicalConfigFilePath)) return noopFileWatcher; 1398 } 1399 const fileWatcher = this.projectService.watchFactory.watchFile( 1400 missingFilePath, 1401 (fileName, eventKind) => { 1402 if (isConfiguredProject(this)) { 1403 this.getCachedDirectoryStructureHost().addOrDeleteFile(fileName, missingFilePath, eventKind); 1404 } 1405 1406 if (eventKind === FileWatcherEventKind.Created && this.missingFilesMap!.has(missingFilePath)) { 1407 this.missingFilesMap!.delete(missingFilePath); 1408 fileWatcher.close(); 1409 1410 // When a missing file is created, we should update the graph. 1411 this.projectService.delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(this); 1412 } 1413 }, 1414 PollingInterval.Medium, 1415 this.projectService.getWatchOptions(this), 1416 WatchType.MissingFile, 1417 this 1418 ); 1419 return fileWatcher; 1420 } 1421 1422 private isWatchedMissingFile(path: Path) { 1423 return !!this.missingFilesMap && this.missingFilesMap.has(path); 1424 } 1425 1426 /* @internal */ 1427 addGeneratedFileWatch(generatedFile: string, sourceFile: string) { 1428 if (outFile(this.compilerOptions)) { 1429 // Single watcher 1430 if (!this.generatedFilesMap) { 1431 this.generatedFilesMap = this.createGeneratedFileWatcher(generatedFile); 1432 } 1433 } 1434 else { 1435 // Map 1436 const path = this.toPath(sourceFile); 1437 if (this.generatedFilesMap) { 1438 if (isGeneratedFileWatcher(this.generatedFilesMap)) { 1439 Debug.fail(`${this.projectName} Expected to not have --out watcher for generated file with options: ${JSON.stringify(this.compilerOptions)}`); 1440 } 1441 if (this.generatedFilesMap.has(path)) return; 1442 } 1443 else { 1444 this.generatedFilesMap = new Map(); 1445 } 1446 this.generatedFilesMap.set(path, this.createGeneratedFileWatcher(generatedFile)); 1447 } 1448 } 1449 1450 private createGeneratedFileWatcher(generatedFile: string): GeneratedFileWatcher { 1451 return { 1452 generatedFilePath: this.toPath(generatedFile), 1453 watcher: this.projectService.watchFactory.watchFile( 1454 generatedFile, 1455 () => { 1456 this.clearSourceMapperCache(); 1457 this.projectService.delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(this); 1458 }, 1459 PollingInterval.High, 1460 this.projectService.getWatchOptions(this), 1461 WatchType.MissingGeneratedFile, 1462 this 1463 ) 1464 }; 1465 } 1466 1467 private isValidGeneratedFileWatcher(generateFile: string, watcher: GeneratedFileWatcher) { 1468 return this.toPath(generateFile) === watcher.generatedFilePath; 1469 } 1470 1471 private clearGeneratedFileWatch() { 1472 if (this.generatedFilesMap) { 1473 if (isGeneratedFileWatcher(this.generatedFilesMap)) { 1474 closeFileWatcherOf(this.generatedFilesMap); 1475 } 1476 else { 1477 clearMap(this.generatedFilesMap, closeFileWatcherOf); 1478 } 1479 this.generatedFilesMap = undefined; 1480 } 1481 } 1482 1483 getScriptInfoForNormalizedPath(fileName: NormalizedPath): ScriptInfo | undefined { 1484 const scriptInfo = this.projectService.getScriptInfoForPath(this.toPath(fileName)); 1485 if (scriptInfo && !scriptInfo.isAttached(this)) { 1486 return Errors.ThrowProjectDoesNotContainDocument(fileName, this); 1487 } 1488 return scriptInfo; 1489 } 1490 1491 getScriptInfo(uncheckedFileName: string) { 1492 return this.projectService.getScriptInfo(uncheckedFileName); 1493 } 1494 1495 filesToString(writeProjectFileNames: boolean) { 1496 if (this.isInitialLoadPending()) return "\tFiles (0) InitialLoadPending\n"; 1497 if (!this.program) return "\tFiles (0) NoProgram\n"; 1498 const sourceFiles = this.program.getSourceFiles(); 1499 let strBuilder = `\tFiles (${sourceFiles.length})\n`; 1500 if (writeProjectFileNames) { 1501 for (const file of sourceFiles) { 1502 strBuilder += `\t${file.fileName}\n`; 1503 } 1504 strBuilder += "\n\n"; 1505 explainFiles(this.program, s => strBuilder += `\t${s}\n`); 1506 } 1507 return strBuilder; 1508 } 1509 1510 /*@internal*/ 1511 print(writeProjectFileNames: boolean) { 1512 this.writeLog(`Project '${this.projectName}' (${ProjectKind[this.projectKind]})`); 1513 this.writeLog(this.filesToString(writeProjectFileNames && this.projectService.logger.hasLevel(LogLevel.verbose))); 1514 this.writeLog("-----------------------------------------------"); 1515 if (this.autoImportProviderHost) { 1516 this.autoImportProviderHost.print(/*writeProjectFileNames*/ false); 1517 } 1518 } 1519 1520 setCompilerOptions(compilerOptions: CompilerOptions) { 1521 if (compilerOptions) { 1522 compilerOptions.allowNonTsExtensions = true; 1523 const oldOptions = this.compilerOptions; 1524 this.compilerOptions = compilerOptions; 1525 this.setInternalCompilerOptionsForEmittingJsFiles(); 1526 this.noDtsResolutionProject?.setCompilerOptions(this.getCompilerOptionsForNoDtsResolutionProject()); 1527 if (changesAffectModuleResolution(oldOptions, compilerOptions)) { 1528 // reset cached unresolved imports if changes in compiler options affected module resolution 1529 this.cachedUnresolvedImportsPerFile.clear(); 1530 this.lastCachedUnresolvedImportsList = undefined; 1531 this.resolutionCache.clear(); 1532 this.moduleSpecifierCache.clear(); 1533 } 1534 this.markAsDirty(); 1535 } 1536 } 1537 1538 /*@internal*/ 1539 setWatchOptions(watchOptions: WatchOptions | undefined) { 1540 this.watchOptions = watchOptions; 1541 } 1542 1543 /*@internal*/ 1544 getWatchOptions(): WatchOptions | undefined { 1545 return this.watchOptions; 1546 } 1547 1548 setTypeAcquisition(newTypeAcquisition: TypeAcquisition | undefined): void { 1549 if (newTypeAcquisition) { 1550 this.typeAcquisition = this.removeLocalTypingsFromTypeAcquisition(newTypeAcquisition); 1551 } 1552 } 1553 1554 getTypeAcquisition() { 1555 return this.typeAcquisition || {}; 1556 } 1557 1558 /* @internal */ 1559 getChangesSinceVersion(lastKnownVersion?: number, includeProjectReferenceRedirectInfo?: boolean): ProjectFilesWithTSDiagnostics { 1560 const includeProjectReferenceRedirectInfoIfRequested = 1561 includeProjectReferenceRedirectInfo 1562 ? (files: ESMap<string, boolean>) => arrayFrom(files.entries(), ([fileName, isSourceOfProjectReferenceRedirect]): protocol.FileWithProjectReferenceRedirectInfo => ({ 1563 fileName, 1564 isSourceOfProjectReferenceRedirect 1565 })) 1566 : (files: ESMap<string, boolean>) => arrayFrom(files.keys()); 1567 1568 // Update the graph only if initial configured project load is not pending 1569 if (!this.isInitialLoadPending()) { 1570 updateProjectIfDirty(this); 1571 } 1572 1573 const info: protocol.ProjectVersionInfo = { 1574 projectName: this.getProjectName(), 1575 version: this.projectProgramVersion, 1576 isInferred: isInferredProject(this), 1577 options: this.getCompilationSettings(), 1578 languageServiceDisabled: !this.languageServiceEnabled, 1579 lastFileExceededProgramSize: this.lastFileExceededProgramSize 1580 }; 1581 const updatedFileNames = this.updatedFileNames; 1582 this.updatedFileNames = undefined; 1583 // check if requested version is the same that we have reported last time 1584 if (this.lastReportedFileNames && lastKnownVersion === this.lastReportedVersion) { 1585 // if current structure version is the same - return info without any changes 1586 if (this.projectProgramVersion === this.lastReportedVersion && !updatedFileNames) { 1587 return { info, projectErrors: this.getGlobalProjectErrors() }; 1588 } 1589 // compute and return the difference 1590 const lastReportedFileNames = this.lastReportedFileNames; 1591 const externalFiles = this.getExternalFiles().map((f): protocol.FileWithProjectReferenceRedirectInfo => ({ 1592 fileName: toNormalizedPath(f), 1593 isSourceOfProjectReferenceRedirect: false 1594 })); 1595 const currentFiles = arrayToMap( 1596 this.getFileNamesWithRedirectInfo(!!includeProjectReferenceRedirectInfo).concat(externalFiles), 1597 info => info.fileName, 1598 info => info.isSourceOfProjectReferenceRedirect 1599 ); 1600 1601 const added: ESMap<string, boolean> = new Map<string, boolean>(); 1602 const removed: ESMap<string, boolean> = new Map<string, boolean>(); 1603 1604 const updated: string[] = updatedFileNames ? arrayFrom(updatedFileNames.keys()) : []; 1605 const updatedRedirects: protocol.FileWithProjectReferenceRedirectInfo[] = []; 1606 1607 forEachEntry(currentFiles, (isSourceOfProjectReferenceRedirect, fileName) => { 1608 if (!lastReportedFileNames.has(fileName)) { 1609 added.set(fileName, isSourceOfProjectReferenceRedirect); 1610 } 1611 else if (includeProjectReferenceRedirectInfo && isSourceOfProjectReferenceRedirect !== lastReportedFileNames.get(fileName)){ 1612 updatedRedirects.push({ 1613 fileName, 1614 isSourceOfProjectReferenceRedirect 1615 }); 1616 } 1617 }); 1618 forEachEntry(lastReportedFileNames, (isSourceOfProjectReferenceRedirect, fileName) => { 1619 if (!currentFiles.has(fileName)) { 1620 removed.set(fileName, isSourceOfProjectReferenceRedirect); 1621 } 1622 }); 1623 this.lastReportedFileNames = currentFiles; 1624 this.lastReportedVersion = this.projectProgramVersion; 1625 return { 1626 info, 1627 changes: { 1628 added: includeProjectReferenceRedirectInfoIfRequested(added), 1629 removed: includeProjectReferenceRedirectInfoIfRequested(removed), 1630 updated: includeProjectReferenceRedirectInfo 1631 ? updated.map((fileName): protocol.FileWithProjectReferenceRedirectInfo => ({ 1632 fileName, 1633 isSourceOfProjectReferenceRedirect: this.isSourceOfProjectReferenceRedirect(fileName) 1634 })) 1635 : updated, 1636 updatedRedirects: includeProjectReferenceRedirectInfo ? updatedRedirects : undefined 1637 }, 1638 projectErrors: this.getGlobalProjectErrors() 1639 }; 1640 } 1641 else { 1642 // unknown version - return everything 1643 const projectFileNames = this.getFileNamesWithRedirectInfo(!!includeProjectReferenceRedirectInfo); 1644 const externalFiles = this.getExternalFiles().map((f): protocol.FileWithProjectReferenceRedirectInfo => ({ 1645 fileName: toNormalizedPath(f), 1646 isSourceOfProjectReferenceRedirect: false 1647 })); 1648 const allFiles = projectFileNames.concat(externalFiles); 1649 this.lastReportedFileNames = arrayToMap( 1650 allFiles, 1651 info => info.fileName, 1652 info => info.isSourceOfProjectReferenceRedirect 1653 ); 1654 this.lastReportedVersion = this.projectProgramVersion; 1655 return { 1656 info, 1657 files: includeProjectReferenceRedirectInfo ? allFiles : allFiles.map(f => f.fileName), 1658 projectErrors: this.getGlobalProjectErrors() 1659 }; 1660 } 1661 } 1662 1663 // remove a root file from project 1664 protected removeRoot(info: ScriptInfo): void { 1665 orderedRemoveItem(this.rootFiles, info); 1666 this.rootFilesMap.delete(info.path); 1667 } 1668 1669 /*@internal*/ 1670 isSourceOfProjectReferenceRedirect(fileName: string) { 1671 return !!this.program && this.program.isSourceOfProjectReferenceRedirect(fileName); 1672 } 1673 1674 /*@internal*/ 1675 protected getGlobalPluginSearchPaths() { 1676 // Search any globally-specified probe paths, then our peer node_modules 1677 return [ 1678 ...this.projectService.pluginProbeLocations, 1679 // ../../.. to walk from X/node_modules/typescript/lib/tsserver.js to X/node_modules/ 1680 combinePaths(this.projectService.getExecutingFilePath(), "../../.."), 1681 ]; 1682 } 1683 1684 protected enableGlobalPlugins(options: CompilerOptions, pluginConfigOverrides: Map<any> | undefined): void { 1685 if (!this.projectService.globalPlugins.length) return; 1686 const host = this.projectService.host; 1687 1688 if (!host.require && !host.importPlugin) { 1689 this.projectService.logger.info("Plugins were requested but not running in environment that supports 'require'. Nothing will be loaded"); 1690 return; 1691 } 1692 1693 // Enable global plugins with synthetic configuration entries 1694 const searchPaths = this.getGlobalPluginSearchPaths(); 1695 for (const globalPluginName of this.projectService.globalPlugins) { 1696 // Skip empty names from odd commandline parses 1697 if (!globalPluginName) continue; 1698 1699 // Skip already-locally-loaded plugins 1700 if (options.plugins && options.plugins.some(p => p.name === globalPluginName)) continue; 1701 1702 // Provide global: true so plugins can detect why they can't find their config 1703 this.projectService.logger.info(`Loading global plugin ${globalPluginName}`); 1704 1705 this.enablePlugin({ name: globalPluginName, global: true } as PluginImport, searchPaths, pluginConfigOverrides); 1706 } 1707 } 1708 1709 /** 1710 * Performs the initial steps of enabling a plugin by finding and instantiating the module for a plugin synchronously using 'require'. 1711 */ 1712 /*@internal*/ 1713 beginEnablePluginSync(pluginConfigEntry: PluginImport, searchPaths: string[], pluginConfigOverrides: Map<any> | undefined): BeginEnablePluginResult { 1714 Debug.assertIsDefined(this.projectService.host.require); 1715 1716 let errorLogs: string[] | undefined; 1717 const log = (message: string) => this.projectService.logger.info(message); 1718 const logError = (message: string) => { 1719 (errorLogs ??= []).push(message); 1720 }; 1721 const resolvedModule = firstDefined(searchPaths, searchPath => 1722 Project.resolveModule(pluginConfigEntry.name, searchPath, this.projectService.host, log, logError) as PluginModuleFactory | undefined); 1723 return { pluginConfigEntry, pluginConfigOverrides, resolvedModule, errorLogs }; 1724 } 1725 1726 /** 1727 * Performs the initial steps of enabling a plugin by finding and instantiating the module for a plugin asynchronously using dynamic `import`. 1728 */ 1729 /*@internal*/ 1730 async beginEnablePluginAsync(pluginConfigEntry: PluginImport, searchPaths: string[], pluginConfigOverrides: Map<any> | undefined): Promise<BeginEnablePluginResult> { 1731 Debug.assertIsDefined(this.projectService.host.importPlugin); 1732 1733 let errorLogs: string[] | undefined; 1734 const log = (message: string) => this.projectService.logger.info(message); 1735 const logError = (message: string) => { 1736 (errorLogs ??= []).push(message); 1737 }; 1738 1739 let resolvedModule: PluginModuleFactory | undefined; 1740 for (const searchPath of searchPaths) { 1741 resolvedModule = await Project.importServicePluginAsync(pluginConfigEntry.name, searchPath, this.projectService.host, log, logError) as PluginModuleFactory | undefined; 1742 if (resolvedModule !== undefined) { 1743 break; 1744 } 1745 } 1746 return { pluginConfigEntry, pluginConfigOverrides, resolvedModule, errorLogs }; 1747 } 1748 1749 /** 1750 * Performs the remaining steps of enabling a plugin after its module has been instantiated. 1751 */ 1752 /*@internal*/ 1753 endEnablePlugin({ pluginConfigEntry, pluginConfigOverrides, resolvedModule, errorLogs }: BeginEnablePluginResult) { 1754 if (resolvedModule) { 1755 const configurationOverride = pluginConfigOverrides && pluginConfigOverrides.get(pluginConfigEntry.name); 1756 if (configurationOverride) { 1757 // Preserve the name property since it's immutable 1758 const pluginName = pluginConfigEntry.name; 1759 pluginConfigEntry = configurationOverride; 1760 pluginConfigEntry.name = pluginName; 1761 } 1762 1763 this.enableProxy(resolvedModule, pluginConfigEntry); 1764 } 1765 else { 1766 forEach(errorLogs, message => this.projectService.logger.info(message)); 1767 this.projectService.logger.info(`Couldn't find ${pluginConfigEntry.name}`); 1768 } 1769 } 1770 1771 protected enablePlugin(pluginConfigEntry: PluginImport, searchPaths: string[], pluginConfigOverrides: Map<any> | undefined): void { 1772 this.projectService.requestEnablePlugin(this, pluginConfigEntry, searchPaths, pluginConfigOverrides); 1773 } 1774 1775 private enableProxy(pluginModuleFactory: PluginModuleFactory, configEntry: PluginImport) { 1776 try { 1777 if (typeof pluginModuleFactory !== "function") { 1778 this.projectService.logger.info(`Skipped loading plugin ${configEntry.name} because it did not expose a proper factory function`); 1779 return; 1780 } 1781 1782 const info: PluginCreateInfo = { 1783 config: configEntry, 1784 project: this, 1785 languageService: this.languageService, 1786 languageServiceHost: this, 1787 serverHost: this.projectService.host, 1788 session: this.projectService.session 1789 }; 1790 1791 const pluginModule = pluginModuleFactory({ typescript: ts }); 1792 const newLS = pluginModule.create(info); 1793 for (const k of Object.keys(this.languageService)) { 1794 // eslint-disable-next-line local/no-in-operator 1795 if (!(k in newLS)) { 1796 this.projectService.logger.info(`Plugin activation warning: Missing proxied method ${k} in created LS. Patching.`); 1797 (newLS as any)[k] = (this.languageService as any)[k]; 1798 } 1799 } 1800 this.projectService.logger.info(`Plugin validation succeeded`); 1801 this.languageService = newLS; 1802 this.plugins.push({ name: configEntry.name, module: pluginModule }); 1803 } 1804 catch (e) { 1805 this.projectService.logger.info(`Plugin activation failed: ${e}`); 1806 } 1807 } 1808 1809 /*@internal*/ 1810 onPluginConfigurationChanged(pluginName: string, configuration: any) { 1811 this.plugins.filter(plugin => plugin.name === pluginName).forEach(plugin => { 1812 if (plugin.module.onConfigurationChanged) { 1813 plugin.module.onConfigurationChanged(configuration); 1814 } 1815 }); 1816 } 1817 1818 /** Starts a new check for diagnostics. Call this if some file has updated that would cause diagnostics to be changed. */ 1819 refreshDiagnostics() { 1820 this.projectService.sendProjectsUpdatedInBackgroundEvent(); 1821 } 1822 1823 /*@internal*/ 1824 getPackageJsonsVisibleToFile(fileName: string, rootDir?: string): readonly ProjectPackageJsonInfo[] { 1825 if (this.projectService.serverMode !== LanguageServiceMode.Semantic) return emptyArray; 1826 return this.projectService.getPackageJsonsVisibleToFile(fileName, rootDir); 1827 } 1828 1829 /*@internal*/ 1830 getNearestAncestorDirectoryWithPackageJson(fileName: string): string | undefined { 1831 return this.projectService.getNearestAncestorDirectoryWithPackageJson(fileName); 1832 } 1833 1834 /*@internal*/ 1835 getPackageJsonsForAutoImport(rootDir?: string): readonly ProjectPackageJsonInfo[] { 1836 const packageJsons = this.getPackageJsonsVisibleToFile(combinePaths(this.currentDirectory, inferredTypesContainingFile), rootDir); 1837 this.packageJsonsForAutoImport = new Set(packageJsons.map(p => p.fileName)); 1838 return packageJsons; 1839 } 1840 1841 /* @internal */ 1842 getPackageJsonCache() { 1843 return this.projectService.packageJsonCache; 1844 } 1845 1846 /*@internal*/ 1847 getCachedExportInfoMap() { 1848 return this.exportMapCache ||= createCacheableExportInfoMap(this); 1849 } 1850 1851 /*@internal*/ 1852 clearCachedExportInfoMap() { 1853 this.exportMapCache?.clear(); 1854 } 1855 1856 /*@internal*/ 1857 getModuleSpecifierCache() { 1858 return this.moduleSpecifierCache; 1859 } 1860 1861 /*@internal*/ 1862 includePackageJsonAutoImports(): PackageJsonAutoImportPreference { 1863 if (this.projectService.includePackageJsonAutoImports() === PackageJsonAutoImportPreference.Off || 1864 !this.languageServiceEnabled || 1865 isInsideNodeModules(this.currentDirectory) || 1866 !this.isDefaultProjectForOpenFiles()) { 1867 return PackageJsonAutoImportPreference.Off; 1868 } 1869 return this.projectService.includePackageJsonAutoImports(); 1870 } 1871 1872 /*@internal*/ 1873 getModuleResolutionHostForAutoImportProvider(): ModuleResolutionHost { 1874 if (this.program) { 1875 return { 1876 fileExists: this.program.fileExists, 1877 directoryExists: this.program.directoryExists, 1878 realpath: this.program.realpath || this.projectService.host.realpath?.bind(this.projectService.host), 1879 getCurrentDirectory: this.getCurrentDirectory.bind(this), 1880 readFile: this.projectService.host.readFile.bind(this.projectService.host), 1881 getDirectories: this.projectService.host.getDirectories.bind(this.projectService.host), 1882 trace: this.projectService.host.trace?.bind(this.projectService.host), 1883 useCaseSensitiveFileNames: this.program.useCaseSensitiveFileNames(), 1884 }; 1885 } 1886 return this.projectService.host; 1887 } 1888 1889 /*@internal*/ 1890 getPackageJsonAutoImportProvider(): Program | undefined { 1891 if (this.autoImportProviderHost === false) { 1892 return undefined; 1893 } 1894 if (this.projectService.serverMode !== LanguageServiceMode.Semantic) { 1895 this.autoImportProviderHost = false; 1896 return undefined; 1897 } 1898 if (this.autoImportProviderHost) { 1899 updateProjectIfDirty(this.autoImportProviderHost); 1900 if (this.autoImportProviderHost.isEmpty()) { 1901 this.autoImportProviderHost.close(); 1902 this.autoImportProviderHost = undefined; 1903 return undefined; 1904 } 1905 return this.autoImportProviderHost.getCurrentProgram(); 1906 } 1907 1908 const dependencySelection = this.includePackageJsonAutoImports(); 1909 if (dependencySelection) { 1910 tracing?.push(tracing.Phase.Session, "getPackageJsonAutoImportProvider"); 1911 const start = timestamp(); 1912 this.autoImportProviderHost = AutoImportProviderProject.create(dependencySelection, this, this.getModuleResolutionHostForAutoImportProvider(), this.documentRegistry); 1913 if (this.autoImportProviderHost) { 1914 updateProjectIfDirty(this.autoImportProviderHost); 1915 this.sendPerformanceEvent("CreatePackageJsonAutoImportProvider", timestamp() - start); 1916 tracing?.pop(); 1917 return this.autoImportProviderHost.getCurrentProgram(); 1918 } 1919 tracing?.pop(); 1920 } 1921 } 1922 1923 /*@internal*/ 1924 private isDefaultProjectForOpenFiles(): boolean { 1925 return !!forEachEntry( 1926 this.projectService.openFiles, 1927 (_, fileName) => this.projectService.tryGetDefaultProjectForFile(toNormalizedPath(fileName)) === this); 1928 } 1929 1930 /*@internal*/ 1931 watchNodeModulesForPackageJsonChanges(directoryPath: string) { 1932 return this.projectService.watchPackageJsonsInNodeModules(this.toPath(directoryPath), this); 1933 } 1934 1935 /*@internal*/ 1936 getIncompleteCompletionsCache() { 1937 return this.projectService.getIncompleteCompletionsCache(); 1938 } 1939 1940 /*@internal*/ 1941 getNoDtsResolutionProject(rootFileNames: readonly string[]): Project { 1942 Debug.assert(this.projectService.serverMode === LanguageServiceMode.Semantic); 1943 if (!this.noDtsResolutionProject) { 1944 this.noDtsResolutionProject = new AuxiliaryProject(this.projectService, this.documentRegistry, this.getCompilerOptionsForNoDtsResolutionProject()); 1945 } 1946 1947 enumerateInsertsAndDeletes<NormalizedPath, NormalizedPath>( 1948 rootFileNames.map(toNormalizedPath), 1949 this.noDtsResolutionProject.getRootFiles(), 1950 getStringComparer(!this.useCaseSensitiveFileNames()), 1951 pathToAdd => { 1952 const info = this.projectService.getOrCreateScriptInfoNotOpenedByClient( 1953 pathToAdd, 1954 this.currentDirectory, 1955 this.noDtsResolutionProject!.directoryStructureHost); 1956 if (info) { 1957 this.noDtsResolutionProject!.addRoot(info, pathToAdd); 1958 } 1959 }, 1960 pathToRemove => { 1961 // It may be preferable to remove roots only once project grows to a certain size? 1962 const info = this.noDtsResolutionProject!.getScriptInfo(pathToRemove); 1963 if (info) { 1964 this.noDtsResolutionProject!.removeRoot(info); 1965 } 1966 }, 1967 ); 1968 1969 return this.noDtsResolutionProject; 1970 } 1971 1972 /*@internal*/ 1973 private getCompilerOptionsForNoDtsResolutionProject() { 1974 return { 1975 ...this.getCompilerOptions(), 1976 noDtsResolution: true, 1977 allowJs: true, 1978 maxNodeModuleJsDepth: 3, 1979 diagnostics: false, 1980 skipLibCheck: true, 1981 sourceMap: false, 1982 types: ts.emptyArray, 1983 lib: ts.emptyArray, 1984 noLib: true, 1985 }; 1986 } 1987 } 1988 1989 function getUnresolvedImports(program: Program, cachedUnresolvedImportsPerFile: ESMap<Path, readonly string[]>): SortedReadonlyArray<string> { 1990 const sourceFiles = program.getSourceFiles(); 1991 tracing?.push(tracing.Phase.Session, "getUnresolvedImports", { count: sourceFiles.length }); 1992 const ambientModules = program.getTypeChecker().getAmbientModules().map(mod => stripQuotes(mod.getName())); 1993 const result = sortAndDeduplicate(flatMap(sourceFiles, sourceFile => 1994 extractUnresolvedImportsFromSourceFile(sourceFile, ambientModules, cachedUnresolvedImportsPerFile))); 1995 tracing?.pop(); 1996 return result; 1997 } 1998 function extractUnresolvedImportsFromSourceFile(file: SourceFile, ambientModules: readonly string[], cachedUnresolvedImportsPerFile: ESMap<Path, readonly string[]>): readonly string[] { 1999 return getOrUpdate(cachedUnresolvedImportsPerFile, file.path, () => { 2000 if (!file.resolvedModules) return emptyArray; 2001 let unresolvedImports: string[] | undefined; 2002 file.resolvedModules.forEach((resolvedModule, name) => { 2003 // pick unresolved non-relative names 2004 if ((!resolvedModule || !resolutionExtensionIsTSOrJson(resolvedModule.extension)) && 2005 !isExternalModuleNameRelative(name) && 2006 !ambientModules.some(m => m === name)) { 2007 unresolvedImports = append(unresolvedImports, parsePackageName(name).packageName); 2008 } 2009 }); 2010 return unresolvedImports || emptyArray; 2011 }); 2012 } 2013 2014 /** 2015 * If a file is opened and no tsconfig (or jsconfig) is found, 2016 * the file and its imports/references are put into an InferredProject. 2017 */ 2018 export class InferredProject extends Project { 2019 private _isJsInferredProject = false; 2020 2021 toggleJsInferredProject(isJsInferredProject: boolean) { 2022 if (isJsInferredProject !== this._isJsInferredProject) { 2023 this._isJsInferredProject = isJsInferredProject; 2024 this.setCompilerOptions(); 2025 } 2026 } 2027 2028 setCompilerOptions(options?: CompilerOptions) { 2029 // Avoid manipulating the given options directly 2030 if (!options && !this.getCompilationSettings()) { 2031 return; 2032 } 2033 const newOptions = cloneCompilerOptions(options || this.getCompilationSettings()); 2034 if (this._isJsInferredProject && typeof newOptions.maxNodeModuleJsDepth !== "number") { 2035 newOptions.maxNodeModuleJsDepth = 2; 2036 } 2037 else if (!this._isJsInferredProject) { 2038 newOptions.maxNodeModuleJsDepth = undefined; 2039 } 2040 newOptions.allowJs = true; 2041 super.setCompilerOptions(newOptions); 2042 } 2043 2044 /** this is canonical project root path */ 2045 readonly projectRootPath: string | undefined; 2046 2047 /*@internal*/ 2048 /** stored only if their is no projectRootPath and this isnt single inferred project */ 2049 readonly canonicalCurrentDirectory: string | undefined; 2050 2051 /*@internal*/ 2052 constructor( 2053 projectService: ProjectService, 2054 documentRegistry: DocumentRegistry, 2055 compilerOptions: CompilerOptions, 2056 watchOptions: WatchOptions | undefined, 2057 projectRootPath: NormalizedPath | undefined, 2058 currentDirectory: string | undefined, 2059 pluginConfigOverrides: ESMap<string, any> | undefined, 2060 typeAcquisition: TypeAcquisition | undefined) { 2061 super(projectService.newInferredProjectName(), 2062 ProjectKind.Inferred, 2063 projectService, 2064 documentRegistry, 2065 // TODO: GH#18217 2066 /*files*/ undefined!, 2067 /*lastFileExceededProgramSize*/ undefined, 2068 compilerOptions, 2069 /*compileOnSaveEnabled*/ false, 2070 watchOptions, 2071 projectService.host, 2072 currentDirectory); 2073 this.typeAcquisition = typeAcquisition; 2074 this.projectRootPath = projectRootPath && projectService.toCanonicalFileName(projectRootPath); 2075 if (!projectRootPath && !projectService.useSingleInferredProject) { 2076 this.canonicalCurrentDirectory = projectService.toCanonicalFileName(this.currentDirectory); 2077 } 2078 this.enableGlobalPlugins(this.getCompilerOptions(), pluginConfigOverrides); 2079 } 2080 2081 addRoot(info: ScriptInfo) { 2082 Debug.assert(info.isScriptOpen()); 2083 this.projectService.startWatchingConfigFilesForInferredProjectRoot(info); 2084 if (!this._isJsInferredProject && info.isJavaScript()) { 2085 this.toggleJsInferredProject(/*isJsInferredProject*/ true); 2086 } 2087 super.addRoot(info); 2088 } 2089 2090 removeRoot(info: ScriptInfo) { 2091 this.projectService.stopWatchingConfigFilesForInferredProjectRoot(info); 2092 super.removeRoot(info); 2093 if (this._isJsInferredProject && info.isJavaScript()) { 2094 if (every(this.getRootScriptInfos(), rootInfo => !rootInfo.isJavaScript())) { 2095 this.toggleJsInferredProject(/*isJsInferredProject*/ false); 2096 } 2097 } 2098 } 2099 2100 /*@internal*/ 2101 isOrphan() { 2102 return !this.hasRoots(); 2103 } 2104 2105 isProjectWithSingleRoot() { 2106 // - when useSingleInferredProject is not set and projectRootPath is not set, 2107 // we can guarantee that this will be the only root 2108 // - other wise it has single root if it has single root script info 2109 return (!this.projectRootPath && !this.projectService.useSingleInferredProject) || 2110 this.getRootScriptInfos().length === 1; 2111 } 2112 2113 close() { 2114 forEach(this.getRootScriptInfos(), info => this.projectService.stopWatchingConfigFilesForInferredProjectRoot(info)); 2115 super.close(); 2116 } 2117 2118 getTypeAcquisition(): TypeAcquisition { 2119 return this.typeAcquisition || { 2120 enable: allRootFilesAreJsOrDts(this), 2121 include: ts.emptyArray, 2122 exclude: ts.emptyArray 2123 }; 2124 } 2125 } 2126 2127 class AuxiliaryProject extends Project { 2128 constructor(projectService: ProjectService, documentRegistry: DocumentRegistry, compilerOptions: CompilerOptions) { 2129 super(projectService.newAuxiliaryProjectName(), 2130 ProjectKind.Auxiliary, 2131 projectService, 2132 documentRegistry, 2133 /*hasExplicitListOfFiles*/ false, 2134 /*lastFileExceededProgramSize*/ undefined, 2135 compilerOptions, 2136 /*compileOnSaveEnabled*/ false, 2137 /*watchOptions*/ undefined, 2138 projectService.host, 2139 /*currentDirectory*/ undefined); 2140 } 2141 2142 isOrphan(): boolean { 2143 return true; 2144 } 2145 2146 /*@internal*/ 2147 scheduleInvalidateResolutionsOfFailedLookupLocations(): void { 2148 // Invalidation will happen on-demand as part of updateGraph 2149 return; 2150 } 2151 } 2152 2153 export class AutoImportProviderProject extends Project { 2154 /*@internal*/ 2155 private static readonly maxDependencies = 10; 2156 2157 /*@internal*/ 2158 static getRootFileNames(dependencySelection: PackageJsonAutoImportPreference, hostProject: Project, moduleResolutionHost: ModuleResolutionHost, compilerOptions: CompilerOptions): string[] { 2159 if (!dependencySelection) { 2160 return ts.emptyArray; 2161 } 2162 2163 const program = hostProject.getCurrentProgram(); 2164 if (!program) { 2165 return ts.emptyArray; 2166 } 2167 2168 const start = timestamp(); 2169 let dependencyNames: Set<string> | undefined; 2170 let rootNames: string[] | undefined; 2171 const rootFileName = combinePaths(hostProject.currentDirectory, inferredTypesContainingFile); 2172 const packageJsons = hostProject.getPackageJsonsForAutoImport(combinePaths(hostProject.currentDirectory, rootFileName)); 2173 for (const packageJson of packageJsons) { 2174 packageJson.dependencies?.forEach((_, dependenyName) => addDependency(dependenyName)); 2175 packageJson.peerDependencies?.forEach((_, dependencyName) => addDependency(dependencyName)); 2176 } 2177 2178 let dependenciesAdded = 0; 2179 if (dependencyNames) { 2180 const symlinkCache = hostProject.getSymlinkCache(); 2181 for (const name of arrayFrom(dependencyNames.keys())) { 2182 // Avoid creating a large project that would significantly slow down time to editor interactivity 2183 if (dependencySelection === PackageJsonAutoImportPreference.Auto && dependenciesAdded > this.maxDependencies) { 2184 hostProject.log(`AutoImportProviderProject: attempted to add more than ${this.maxDependencies} dependencies. Aborting.`); 2185 return ts.emptyArray; 2186 } 2187 2188 // 1. Try to load from the implementation package. For many dependencies, the 2189 // package.json will exist, but the package will not contain any typings, 2190 // so `entrypoints` will be undefined. In that case, or if the dependency 2191 // is missing altogether, we will move on to trying the @types package (2). 2192 const packageJson = resolvePackageNameToPackageJson( 2193 name, 2194 hostProject.currentDirectory, 2195 compilerOptions, 2196 moduleResolutionHost, 2197 program.getModuleResolutionCache()); 2198 if (packageJson) { 2199 const entrypoints = getRootNamesFromPackageJson(packageJson, program, symlinkCache); 2200 if (entrypoints) { 2201 rootNames = concatenate(rootNames, entrypoints); 2202 dependenciesAdded += entrypoints.length ? 1 : 0; 2203 continue; 2204 } 2205 } 2206 2207 // 2. Try to load from the @types package in the tree and in the global 2208 // typings cache location, if enabled. 2209 const done = forEach([hostProject.currentDirectory, hostProject.getGlobalTypingsCacheLocation()], directory => { 2210 if (directory) { 2211 const typesPackageJson = resolvePackageNameToPackageJson( 2212 `@types/${name}`, 2213 directory, 2214 compilerOptions, 2215 moduleResolutionHost, 2216 program.getModuleResolutionCache()); 2217 if (typesPackageJson) { 2218 const entrypoints = getRootNamesFromPackageJson(typesPackageJson, program, symlinkCache); 2219 rootNames = concatenate(rootNames, entrypoints); 2220 dependenciesAdded += entrypoints?.length ? 1 : 0; 2221 return true; 2222 } 2223 } 2224 }); 2225 2226 if (done) continue; 2227 2228 // 3. If the @types package did not exist and the user has settings that 2229 // allow processing JS from node_modules, go back to the implementation 2230 // package and load the JS. 2231 if (packageJson && compilerOptions.allowJs && compilerOptions.maxNodeModuleJsDepth) { 2232 const entrypoints = getRootNamesFromPackageJson(packageJson, program, symlinkCache, /*allowJs*/ true); 2233 rootNames = concatenate(rootNames, entrypoints); 2234 dependenciesAdded += entrypoints?.length ? 1 : 0; 2235 } 2236 } 2237 } 2238 2239 if (rootNames?.length) { 2240 hostProject.log(`AutoImportProviderProject: found ${rootNames.length} root files in ${dependenciesAdded} dependencies in ${timestamp() - start} ms`); 2241 } 2242 return rootNames || ts.emptyArray; 2243 2244 function addDependency(dependency: string) { 2245 if (!startsWith(dependency, "@types/")) { 2246 (dependencyNames || (dependencyNames = new Set())).add(dependency); 2247 } 2248 } 2249 2250 function getRootNamesFromPackageJson(packageJson: PackageJsonInfo, program: Program, symlinkCache: SymlinkCache, resolveJs?: boolean) { 2251 const entrypoints = getEntrypointsFromPackageJsonInfo( 2252 packageJson, 2253 compilerOptions, 2254 moduleResolutionHost, 2255 program.getModuleResolutionCache(), 2256 resolveJs); 2257 if (entrypoints) { 2258 const real = moduleResolutionHost.realpath?.(packageJson.packageDirectory); 2259 const isSymlink = real && real !== packageJson.packageDirectory; 2260 if (isSymlink) { 2261 symlinkCache.setSymlinkedDirectory(packageJson.packageDirectory, { 2262 real, 2263 realPath: hostProject.toPath(real), 2264 }); 2265 } 2266 2267 return mapDefined(entrypoints, entrypoint => { 2268 const resolvedFileName = isSymlink ? entrypoint.replace(packageJson.packageDirectory, real) : entrypoint; 2269 if (!program.getSourceFile(resolvedFileName) && !(isSymlink && program.getSourceFile(entrypoint))) { 2270 return resolvedFileName; 2271 } 2272 }); 2273 } 2274 } 2275 } 2276 2277 /*@internal*/ 2278 static readonly compilerOptionsOverrides: CompilerOptions = { 2279 diagnostics: false, 2280 skipLibCheck: true, 2281 sourceMap: false, 2282 types: ts.emptyArray, 2283 lib: ts.emptyArray, 2284 noLib: true, 2285 }; 2286 2287 /*@internal*/ 2288 static create(dependencySelection: PackageJsonAutoImportPreference, hostProject: Project, moduleResolutionHost: ModuleResolutionHost, documentRegistry: DocumentRegistry): AutoImportProviderProject | undefined { 2289 if (dependencySelection === PackageJsonAutoImportPreference.Off) { 2290 return undefined; 2291 } 2292 2293 const compilerOptions = { 2294 ...hostProject.getCompilerOptions(), 2295 ...this.compilerOptionsOverrides, 2296 }; 2297 2298 const rootNames = this.getRootFileNames(dependencySelection, hostProject, moduleResolutionHost, compilerOptions); 2299 if (!rootNames.length) { 2300 return undefined; 2301 } 2302 2303 return new AutoImportProviderProject(hostProject, rootNames, documentRegistry, compilerOptions); 2304 } 2305 2306 private rootFileNames: string[] | undefined; 2307 2308 /*@internal*/ 2309 constructor( 2310 private hostProject: Project, 2311 initialRootNames: string[], 2312 documentRegistry: DocumentRegistry, 2313 compilerOptions: CompilerOptions, 2314 ) { 2315 super(hostProject.projectService.newAutoImportProviderProjectName(), 2316 ProjectKind.AutoImportProvider, 2317 hostProject.projectService, 2318 documentRegistry, 2319 /*hasExplicitListOfFiles*/ false, 2320 /*lastFileExceededProgramSize*/ undefined, 2321 compilerOptions, 2322 /*compileOnSaveEnabled*/ false, 2323 hostProject.getWatchOptions(), 2324 hostProject.projectService.host, 2325 hostProject.currentDirectory); 2326 2327 this.rootFileNames = initialRootNames; 2328 this.useSourceOfProjectReferenceRedirect = maybeBind(this.hostProject, this.hostProject.useSourceOfProjectReferenceRedirect); 2329 this.getParsedCommandLine = maybeBind(this.hostProject, this.hostProject.getParsedCommandLine); 2330 } 2331 2332 /*@internal*/ 2333 isEmpty() { 2334 return !some(this.rootFileNames); 2335 } 2336 2337 isOrphan() { 2338 return true; 2339 } 2340 2341 updateGraph() { 2342 let rootFileNames = this.rootFileNames; 2343 if (!rootFileNames) { 2344 rootFileNames = AutoImportProviderProject.getRootFileNames( 2345 this.hostProject.includePackageJsonAutoImports(), 2346 this.hostProject, 2347 this.hostProject.getModuleResolutionHostForAutoImportProvider(), 2348 this.getCompilationSettings()); 2349 } 2350 2351 this.projectService.setFileNamesOfAutoImportProviderProject(this, rootFileNames); 2352 this.rootFileNames = rootFileNames; 2353 const oldProgram = this.getCurrentProgram(); 2354 const hasSameSetOfFiles = super.updateGraph(); 2355 if (oldProgram && oldProgram !== this.getCurrentProgram()) { 2356 this.hostProject.clearCachedExportInfoMap(); 2357 } 2358 return hasSameSetOfFiles; 2359 } 2360 2361 /*@internal*/ 2362 scheduleInvalidateResolutionsOfFailedLookupLocations(): void { 2363 // Invalidation will happen on-demand as part of updateGraph 2364 return; 2365 } 2366 2367 hasRoots() { 2368 return !!this.rootFileNames?.length; 2369 } 2370 2371 markAsDirty() { 2372 this.rootFileNames = undefined; 2373 super.markAsDirty(); 2374 } 2375 2376 getScriptFileNames() { 2377 return this.rootFileNames || ts.emptyArray; 2378 } 2379 2380 getLanguageService(): never { 2381 throw new Error("AutoImportProviderProject language service should never be used. To get the program, use `project.getCurrentProgram()`."); 2382 } 2383 2384 /*@internal*/ 2385 onAutoImportProviderSettingsChanged(): never { 2386 throw new Error("AutoImportProviderProject is an auto import provider; use `markAsDirty()` instead."); 2387 } 2388 2389 /*@internal*/ 2390 onPackageJsonChange(): never { 2391 throw new Error("package.json changes should be notified on an AutoImportProvider's host project"); 2392 } 2393 2394 getModuleResolutionHostForAutoImportProvider(): never { 2395 throw new Error("AutoImportProviderProject cannot provide its own host; use `hostProject.getModuleResolutionHostForAutomImportProvider()` instead."); 2396 } 2397 2398 getProjectReferences() { 2399 return this.hostProject.getProjectReferences(); 2400 } 2401 2402 /*@internal*/ 2403 includePackageJsonAutoImports() { 2404 return PackageJsonAutoImportPreference.Off; 2405 } 2406 2407 getTypeAcquisition(): TypeAcquisition { 2408 return { enable: false }; 2409 } 2410 2411 /*@internal*/ 2412 getSymlinkCache() { 2413 return this.hostProject.getSymlinkCache(); 2414 } 2415 2416 /*@internal*/ 2417 getModuleResolutionCache() { 2418 return this.hostProject.getCurrentProgram()?.getModuleResolutionCache(); 2419 } 2420 } 2421 2422 /** 2423 * If a file is opened, the server will look for a tsconfig (or jsconfig) 2424 * and if successful create a ConfiguredProject for it. 2425 * Otherwise it will create an InferredProject. 2426 */ 2427 export class ConfiguredProject extends Project { 2428 /* @internal */ 2429 pendingReload: ConfigFileProgramReloadLevel | undefined; 2430 /* @internal */ 2431 pendingReloadReason: string | undefined; 2432 2433 /* @internal */ 2434 openFileWatchTriggered = new Map<string, ConfigFileProgramReloadLevel>(); 2435 2436 /*@internal*/ 2437 canConfigFileJsonReportNoInputFiles = false; 2438 2439 /** Ref count to the project when opened from external project */ 2440 private externalProjectRefCount = 0; 2441 2442 private projectReferences: readonly ProjectReference[] | undefined; 2443 2444 /** Potential project references before the project is actually loaded (read config file) */ 2445 /*@internal*/ 2446 potentialProjectReferences: Set<string> | undefined; 2447 2448 /*@internal*/ 2449 projectOptions?: ProjectOptions | true; 2450 2451 /*@internal*/ 2452 isInitialLoadPending: () => boolean = returnTrue; 2453 2454 /*@internal*/ 2455 sendLoadingProjectFinish = false; 2456 2457 /*@internal*/ 2458 private compilerHost?: CompilerHost; 2459 2460 /*@internal*/ 2461 constructor(configFileName: NormalizedPath, 2462 readonly canonicalConfigFilePath: NormalizedPath, 2463 projectService: ProjectService, 2464 documentRegistry: DocumentRegistry, 2465 cachedDirectoryStructureHost: CachedDirectoryStructureHost) { 2466 super(configFileName, 2467 ProjectKind.Configured, 2468 projectService, 2469 documentRegistry, 2470 /*hasExplicitListOfFiles*/ false, 2471 /*lastFileExceededProgramSize*/ undefined, 2472 /*compilerOptions*/ {}, 2473 /*compileOnSaveEnabled*/ false, 2474 /*watchOptions*/ undefined, 2475 cachedDirectoryStructureHost, 2476 getDirectoryPath(configFileName) 2477 ); 2478 } 2479 2480 /* @internal */ 2481 setCompilerHost(host: CompilerHost) { 2482 this.compilerHost = host; 2483 } 2484 2485 /* @internal */ 2486 getCompilerHost(): CompilerHost | undefined { 2487 return this.compilerHost; 2488 } 2489 2490 /* @internal */ 2491 useSourceOfProjectReferenceRedirect() { 2492 return this.languageServiceEnabled; 2493 } 2494 2495 /* @internal */ 2496 getParsedCommandLine(fileName: string) { 2497 const configFileName = asNormalizedPath(normalizePath(fileName)); 2498 const canonicalConfigFilePath = asNormalizedPath(this.projectService.toCanonicalFileName(configFileName)); 2499 // Ensure the config file existience info is cached 2500 let configFileExistenceInfo = this.projectService.configFileExistenceInfoCache.get(canonicalConfigFilePath); 2501 if (!configFileExistenceInfo) { 2502 this.projectService.configFileExistenceInfoCache.set(canonicalConfigFilePath, configFileExistenceInfo = { exists: this.projectService.host.fileExists(configFileName) }); 2503 } 2504 // Ensure we have upto date parsed command line 2505 this.projectService.ensureParsedConfigUptoDate(configFileName, canonicalConfigFilePath, configFileExistenceInfo, this); 2506 // Watch wild cards if LS is enabled 2507 if (this.languageServiceEnabled && this.projectService.serverMode === LanguageServiceMode.Semantic) { 2508 this.projectService.watchWildcards(configFileName, configFileExistenceInfo, this); 2509 } 2510 return configFileExistenceInfo.exists ? configFileExistenceInfo.config!.parsedCommandLine : undefined; 2511 } 2512 2513 /* @internal */ 2514 onReleaseParsedCommandLine(fileName: string) { 2515 this.releaseParsedConfig(asNormalizedPath(this.projectService.toCanonicalFileName(asNormalizedPath(normalizePath(fileName))))); 2516 } 2517 2518 /* @internal */ 2519 private releaseParsedConfig(canonicalConfigFilePath: NormalizedPath) { 2520 this.projectService.stopWatchingWildCards(canonicalConfigFilePath, this); 2521 this.projectService.releaseParsedConfig(canonicalConfigFilePath, this); 2522 } 2523 2524 /** 2525 * If the project has reload from disk pending, it reloads (and then updates graph as part of that) instead of just updating the graph 2526 * @returns: true if set of files in the project stays the same and false - otherwise. 2527 */ 2528 updateGraph(): boolean { 2529 const isInitialLoad = this.isInitialLoadPending(); 2530 this.isInitialLoadPending = returnFalse; 2531 const reloadLevel = this.pendingReload; 2532 this.pendingReload = ConfigFileProgramReloadLevel.None; 2533 let result: boolean; 2534 switch (reloadLevel) { 2535 case ConfigFileProgramReloadLevel.Partial: 2536 this.openFileWatchTriggered.clear(); 2537 result = this.projectService.reloadFileNamesOfConfiguredProject(this); 2538 break; 2539 case ConfigFileProgramReloadLevel.Full: 2540 this.openFileWatchTriggered.clear(); 2541 const reason = Debug.checkDefined(this.pendingReloadReason); 2542 this.pendingReloadReason = undefined; 2543 this.projectService.reloadConfiguredProject(this, reason, isInitialLoad, /*clearSemanticCache*/ false); 2544 result = true; 2545 break; 2546 default: 2547 result = super.updateGraph(); 2548 } 2549 this.compilerHost = undefined; 2550 this.projectService.sendProjectLoadingFinishEvent(this); 2551 this.projectService.sendProjectTelemetry(this); 2552 return result; 2553 } 2554 2555 /*@internal*/ 2556 getCachedDirectoryStructureHost() { 2557 return this.directoryStructureHost as CachedDirectoryStructureHost; 2558 } 2559 2560 getConfigFilePath() { 2561 return asNormalizedPath(this.getProjectName()); 2562 } 2563 2564 getProjectReferences(): readonly ProjectReference[] | undefined { 2565 return this.projectReferences; 2566 } 2567 2568 updateReferences(refs: readonly ProjectReference[] | undefined) { 2569 this.projectReferences = refs; 2570 this.potentialProjectReferences = undefined; 2571 } 2572 2573 /*@internal*/ 2574 setPotentialProjectReference(canonicalConfigPath: NormalizedPath) { 2575 Debug.assert(this.isInitialLoadPending()); 2576 (this.potentialProjectReferences || (this.potentialProjectReferences = new Set())).add(canonicalConfigPath); 2577 } 2578 2579 /*@internal*/ 2580 getResolvedProjectReferenceToRedirect(fileName: string): ResolvedProjectReference | undefined { 2581 const program = this.getCurrentProgram(); 2582 return program && program.getResolvedProjectReferenceToRedirect(fileName); 2583 } 2584 2585 /*@internal*/ 2586 forEachResolvedProjectReference<T>( 2587 cb: (resolvedProjectReference: ResolvedProjectReference) => T | undefined 2588 ): T | undefined { 2589 return this.getCurrentProgram()?.forEachResolvedProjectReference(cb); 2590 } 2591 2592 /*@internal*/ 2593 enablePluginsWithOptions(options: CompilerOptions, pluginConfigOverrides: ESMap<string, any> | undefined): void { 2594 this.plugins.length = 0; 2595 if (!options.plugins?.length && !this.projectService.globalPlugins.length) return; 2596 const host = this.projectService.host; 2597 if (!host.require && !host.importPlugin) { 2598 this.projectService.logger.info("Plugins were requested but not running in environment that supports 'require'. Nothing will be loaded"); 2599 return; 2600 } 2601 2602 const searchPaths = this.getGlobalPluginSearchPaths(); 2603 if (this.projectService.allowLocalPluginLoads) { 2604 const local = getDirectoryPath(this.canonicalConfigFilePath); 2605 this.projectService.logger.info(`Local plugin loading enabled; adding ${local} to search paths`); 2606 searchPaths.unshift(local); 2607 } 2608 2609 // Enable tsconfig-specified plugins 2610 if (options.plugins) { 2611 for (const pluginConfigEntry of options.plugins) { 2612 this.enablePlugin(pluginConfigEntry, searchPaths, pluginConfigOverrides); 2613 } 2614 } 2615 2616 return this.enableGlobalPlugins(options, pluginConfigOverrides); 2617 } 2618 2619 /** 2620 * Get the errors that dont have any file name associated 2621 */ 2622 getGlobalProjectErrors(): readonly Diagnostic[] { 2623 return filter(this.projectErrors, diagnostic => !diagnostic.file) || emptyArray; 2624 } 2625 2626 /** 2627 * Get all the project errors 2628 */ 2629 getAllProjectErrors(): readonly Diagnostic[] { 2630 return this.projectErrors || emptyArray; 2631 } 2632 2633 setProjectErrors(projectErrors: Diagnostic[]) { 2634 this.projectErrors = projectErrors; 2635 } 2636 2637 close() { 2638 this.projectService.configFileExistenceInfoCache.forEach((_configFileExistenceInfo, canonicalConfigFilePath) => 2639 this.releaseParsedConfig(canonicalConfigFilePath)); 2640 this.projectErrors = undefined; 2641 this.openFileWatchTriggered.clear(); 2642 this.compilerHost = undefined; 2643 super.close(); 2644 } 2645 2646 /* @internal */ 2647 addExternalProjectReference() { 2648 this.externalProjectRefCount++; 2649 } 2650 2651 /* @internal */ 2652 deleteExternalProjectReference() { 2653 this.externalProjectRefCount--; 2654 } 2655 2656 /* @internal */ 2657 isSolution() { 2658 return this.getRootFilesMap().size === 0 && 2659 !this.canConfigFileJsonReportNoInputFiles; 2660 } 2661 2662 /* @internal */ 2663 /** Find the configured project from the project references in project which contains the info directly */ 2664 getDefaultChildProjectFromProjectWithReferences(info: ScriptInfo) { 2665 return forEachResolvedProjectReferenceProject( 2666 this, 2667 info.path, 2668 child => projectContainsInfoDirectly(child, info) ? 2669 child : 2670 undefined, 2671 ProjectReferenceProjectLoadKind.Find 2672 ); 2673 } 2674 2675 /** Returns true if the project is needed by any of the open script info/external project */ 2676 /* @internal */ 2677 hasOpenRef() { 2678 if (!!this.externalProjectRefCount) { 2679 return true; 2680 } 2681 2682 // Closed project doesnt have any reference 2683 if (this.isClosed()) { 2684 return false; 2685 } 2686 2687 const configFileExistenceInfo = this.projectService.configFileExistenceInfoCache.get(this.canonicalConfigFilePath)!; 2688 if (this.projectService.hasPendingProjectUpdate(this)) { 2689 // If there is pending update for this project, 2690 // we dont know if this project would be needed by any of the open files impacted by this config file 2691 // In that case keep the project alive if there are open files impacted by this project 2692 return !!configFileExistenceInfo.openFilesImpactedByConfigFile?.size; 2693 } 2694 2695 // If there is no pending update for this project, 2696 // We know exact set of open files that get impacted by this configured project as the files in the project 2697 // The project is referenced only if open files impacted by this project are present in this project 2698 return !!configFileExistenceInfo.openFilesImpactedByConfigFile && forEachEntry( 2699 configFileExistenceInfo.openFilesImpactedByConfigFile, 2700 (_value, infoPath) => { 2701 const info = this.projectService.getScriptInfoForPath(infoPath)!; 2702 return this.containsScriptInfo(info) || 2703 !!forEachResolvedProjectReferenceProject( 2704 this, 2705 info.path, 2706 child => child.containsScriptInfo(info), 2707 ProjectReferenceProjectLoadKind.Find 2708 ); 2709 } 2710 ) || false; 2711 } 2712 2713 /*@internal*/ 2714 hasExternalProjectRef() { 2715 return !!this.externalProjectRefCount; 2716 } 2717 2718 getEffectiveTypeRoots() { 2719 return getEffectiveTypeRoots(this.getCompilationSettings(), this.directoryStructureHost) || []; 2720 } 2721 2722 /*@internal*/ 2723 updateErrorOnNoInputFiles(fileNames: string[]) { 2724 updateErrorForNoInputFiles(fileNames, this.getConfigFilePath(), this.getCompilerOptions().configFile!.configFileSpecs!, this.projectErrors!, this.canConfigFileJsonReportNoInputFiles); 2725 } 2726 } 2727 2728 /** 2729 * Project whose configuration is handled externally, such as in a '.csproj'. 2730 * These are created only if a host explicitly calls `openExternalProject`. 2731 */ 2732 export class ExternalProject extends Project { 2733 excludedFiles: readonly NormalizedPath[] = []; 2734 /*@internal*/ 2735 constructor(public externalProjectName: string, 2736 projectService: ProjectService, 2737 documentRegistry: DocumentRegistry, 2738 compilerOptions: CompilerOptions, 2739 lastFileExceededProgramSize: string | undefined, 2740 public compileOnSaveEnabled: boolean, 2741 projectFilePath?: string, 2742 pluginConfigOverrides?: ESMap<string, any>, 2743 watchOptions?: WatchOptions) { 2744 super(externalProjectName, 2745 ProjectKind.External, 2746 projectService, 2747 documentRegistry, 2748 /*hasExplicitListOfFiles*/ true, 2749 lastFileExceededProgramSize, 2750 compilerOptions, 2751 compileOnSaveEnabled, 2752 watchOptions, 2753 projectService.host, 2754 getDirectoryPath(projectFilePath || normalizeSlashes(externalProjectName))); 2755 this.enableGlobalPlugins(this.getCompilerOptions(), pluginConfigOverrides); 2756 } 2757 2758 updateGraph() { 2759 const result = super.updateGraph(); 2760 this.projectService.sendProjectTelemetry(this); 2761 return result; 2762 } 2763 2764 getExcludedFiles() { 2765 return this.excludedFiles; 2766 } 2767 } 2768 2769 /* @internal */ 2770 export function isInferredProject(project: Project): project is InferredProject { 2771 return project.projectKind === ProjectKind.Inferred; 2772 } 2773 2774 /* @internal */ 2775 export function isConfiguredProject(project: Project): project is ConfiguredProject { 2776 return project.projectKind === ProjectKind.Configured; 2777 } 2778 2779 /* @internal */ 2780 export function isExternalProject(project: Project): project is ExternalProject { 2781 return project.projectKind === ProjectKind.External; 2782 } 2783} 2784