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