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