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