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