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