diff --git a/src/services/documentRegistry.ts b/src/services/documentRegistry.ts index ac673085508..d73e4d3b0a1 100644 --- a/src/services/documentRegistry.ts +++ b/src/services/documentRegistry.ts @@ -1,240 +1,240 @@ -namespace ts { - /** - * The document registry represents a store of SourceFile objects that can be shared between - * multiple LanguageService instances. A LanguageService instance holds on the SourceFile (AST) - * of files in the context. - * SourceFile objects account for most of the memory usage by the language service. Sharing - * the same DocumentRegistry instance between different instances of LanguageService allow - * for more efficient memory utilization since all projects will share at least the library - * file (lib.d.ts). - * - * A more advanced use of the document registry is to serialize sourceFile objects to disk - * and re-hydrate them when needed. - * - * To create a default DocumentRegistry, use createDocumentRegistry to create one, and pass it - * to all subsequent createLanguageService calls. - */ - export interface DocumentRegistry { - /** - * Request a stored SourceFile with a given fileName and compilationSettings. - * The first call to acquire will call createLanguageServiceSourceFile to generate - * the SourceFile if was not found in the registry. - * - * @param fileName The name of the file requested - * @param compilationSettings Some compilation settings like target affects the - * shape of a the resulting SourceFile. This allows the DocumentRegistry to store - * multiple copies of the same file for different compilation settings. - * @parm scriptSnapshot Text of the file. Only used if the file was not found - * in the registry and a new one was created. - * @parm version Current version of the file. Only used if the file was not found - * in the registry and a new one was created. - */ - acquireDocument( - fileName: string, - compilationSettings: CompilerOptions, - scriptSnapshot: IScriptSnapshot, - version: string, - scriptKind?: ScriptKind): SourceFile; - - acquireDocumentWithKey( - fileName: string, - path: Path, - compilationSettings: CompilerOptions, - key: DocumentRegistryBucketKey, - scriptSnapshot: IScriptSnapshot, - version: string, - scriptKind?: ScriptKind): SourceFile; - - /** - * Request an updated version of an already existing SourceFile with a given fileName - * and compilationSettings. The update will in-turn call updateLanguageServiceSourceFile - * to get an updated SourceFile. - * - * @param fileName The name of the file requested - * @param compilationSettings Some compilation settings like target affects the - * shape of a the resulting SourceFile. This allows the DocumentRegistry to store - * multiple copies of the same file for different compilation settings. - * @param scriptSnapshot Text of the file. - * @param version Current version of the file. - */ - updateDocument( - fileName: string, - compilationSettings: CompilerOptions, - scriptSnapshot: IScriptSnapshot, - version: string, - scriptKind?: ScriptKind): SourceFile; - - updateDocumentWithKey( - fileName: string, - path: Path, - compilationSettings: CompilerOptions, - key: DocumentRegistryBucketKey, - scriptSnapshot: IScriptSnapshot, - version: string, - scriptKind?: ScriptKind): SourceFile; - - getKeyForCompilationSettings(settings: CompilerOptions): DocumentRegistryBucketKey; - /** - * Informs the DocumentRegistry that a file is not needed any longer. - * - * Note: It is not allowed to call release on a SourceFile that was not acquired from - * this registry originally. - * - * @param fileName The name of the file to be released - * @param compilationSettings The compilation settings used to acquire the file - */ - releaseDocument(fileName: string, compilationSettings: CompilerOptions): void; - - releaseDocumentWithKey(path: Path, key: DocumentRegistryBucketKey): void; - - reportStats(): string; - } - - export type DocumentRegistryBucketKey = string & { __bucketKey: any }; - - interface DocumentRegistryEntry { - sourceFile: SourceFile; - - // The number of language services that this source file is referenced in. When no more - // language services are referencing the file, then the file can be removed from the - // registry. - languageServiceRefCount: number; - owners: string[]; - } - - export function createDocumentRegistry(useCaseSensitiveFileNames?: boolean, currentDirectory = ""): DocumentRegistry { - // Maps from compiler setting target (ES3, ES5, etc.) to all the cached documents we have - // for those settings. - const buckets = createMap>(); - const getCanonicalFileName = createGetCanonicalFileName(!!useCaseSensitiveFileNames); - - function getKeyForCompilationSettings(settings: CompilerOptions): DocumentRegistryBucketKey { - return `_${settings.target}|${settings.module}|${settings.noResolve}|${settings.jsx}|${settings.allowJs}|${settings.baseUrl}|${JSON.stringify(settings.typeRoots)}|${JSON.stringify(settings.rootDirs)}|${JSON.stringify(settings.paths)}`; - } - - function getBucketForCompilationSettings(key: DocumentRegistryBucketKey, createIfMissing: boolean): FileMap { - let bucket = buckets[key]; - if (!bucket && createIfMissing) { - buckets[key] = bucket = createFileMap(); - } - return bucket; - } - - function reportStats() { - const bucketInfoArray = Object.keys(buckets).filter(name => name && name.charAt(0) === "_").map(name => { - const entries = buckets[name]; - const sourceFiles: { name: string; refCount: number; references: string[]; }[] = []; - entries.forEachValue((key, entry) => { - sourceFiles.push({ - name: key, - refCount: entry.languageServiceRefCount, - references: entry.owners.slice(0) - }); - }); - sourceFiles.sort((x, y) => y.refCount - x.refCount); - return { - bucket: name, - sourceFiles - }; - }); - return JSON.stringify(bucketInfoArray, undefined, 2); - } - - function acquireDocument(fileName: string, compilationSettings: CompilerOptions, scriptSnapshot: IScriptSnapshot, version: string, scriptKind?: ScriptKind): SourceFile { - const path = toPath(fileName, currentDirectory, getCanonicalFileName); - const key = getKeyForCompilationSettings(compilationSettings); - return acquireDocumentWithKey(fileName, path, compilationSettings, key, scriptSnapshot, version, scriptKind); - } - - function acquireDocumentWithKey(fileName: string, path: Path, compilationSettings: CompilerOptions, key: DocumentRegistryBucketKey, scriptSnapshot: IScriptSnapshot, version: string, scriptKind?: ScriptKind): SourceFile { - return acquireOrUpdateDocument(fileName, path, compilationSettings, key, scriptSnapshot, version, /*acquiring*/ true, scriptKind); - } - - function updateDocument(fileName: string, compilationSettings: CompilerOptions, scriptSnapshot: IScriptSnapshot, version: string, scriptKind?: ScriptKind): SourceFile { - const path = toPath(fileName, currentDirectory, getCanonicalFileName); - const key = getKeyForCompilationSettings(compilationSettings); - return updateDocumentWithKey(fileName, path, compilationSettings, key, scriptSnapshot, version, scriptKind); - } - - function updateDocumentWithKey(fileName: string, path: Path, compilationSettings: CompilerOptions, key: DocumentRegistryBucketKey, scriptSnapshot: IScriptSnapshot, version: string, scriptKind?: ScriptKind): SourceFile { - return acquireOrUpdateDocument(fileName, path, compilationSettings, key, scriptSnapshot, version, /*acquiring*/ false, scriptKind); - } - - function acquireOrUpdateDocument( - fileName: string, - path: Path, - compilationSettings: CompilerOptions, - key: DocumentRegistryBucketKey, - scriptSnapshot: IScriptSnapshot, - version: string, - acquiring: boolean, - scriptKind?: ScriptKind): SourceFile { - - const bucket = getBucketForCompilationSettings(key, /*createIfMissing*/ true); - let entry = bucket.get(path); - if (!entry) { - Debug.assert(acquiring, "How could we be trying to update a document that the registry doesn't have?"); - - // Have never seen this file with these settings. Create a new source file for it. - const sourceFile = createLanguageServiceSourceFile(fileName, scriptSnapshot, compilationSettings.target, version, /*setNodeParents*/ false, scriptKind); - - entry = { - sourceFile: sourceFile, - languageServiceRefCount: 0, - owners: [] - }; - bucket.set(path, entry); - } - else { - // We have an entry for this file. However, it may be for a different version of - // the script snapshot. If so, update it appropriately. Otherwise, we can just - // return it as is. - if (entry.sourceFile.version !== version) { - entry.sourceFile = updateLanguageServiceSourceFile(entry.sourceFile, scriptSnapshot, version, - scriptSnapshot.getChangeRange(entry.sourceFile.scriptSnapshot)); - } - } - - // If we're acquiring, then this is the first time this LS is asking for this document. - // Increase our ref count so we know there's another LS using the document. If we're - // not acquiring, then that means the LS is 'updating' the file instead, and that means - // it has already acquired the document previously. As such, we do not need to increase - // the ref count. - if (acquiring) { - entry.languageServiceRefCount++; - } - - return entry.sourceFile; - } - - function releaseDocument(fileName: string, compilationSettings: CompilerOptions): void { - const path = toPath(fileName, currentDirectory, getCanonicalFileName); - const key = getKeyForCompilationSettings(compilationSettings); - return releaseDocumentWithKey(path, key); - } - - function releaseDocumentWithKey(path: Path, key: DocumentRegistryBucketKey): void { - const bucket = getBucketForCompilationSettings(key, /*createIfMissing*/false); - Debug.assert(bucket !== undefined); - - const entry = bucket.get(path); - entry.languageServiceRefCount--; - - Debug.assert(entry.languageServiceRefCount >= 0); - if (entry.languageServiceRefCount === 0) { - bucket.remove(path); - } - } - - return { - acquireDocument, - acquireDocumentWithKey, - updateDocument, - updateDocumentWithKey, - releaseDocument, - releaseDocumentWithKey, - reportStats, - getKeyForCompilationSettings - }; - } -} +namespace ts { + /** + * The document registry represents a store of SourceFile objects that can be shared between + * multiple LanguageService instances. A LanguageService instance holds on the SourceFile (AST) + * of files in the context. + * SourceFile objects account for most of the memory usage by the language service. Sharing + * the same DocumentRegistry instance between different instances of LanguageService allow + * for more efficient memory utilization since all projects will share at least the library + * file (lib.d.ts). + * + * A more advanced use of the document registry is to serialize sourceFile objects to disk + * and re-hydrate them when needed. + * + * To create a default DocumentRegistry, use createDocumentRegistry to create one, and pass it + * to all subsequent createLanguageService calls. + */ + export interface DocumentRegistry { + /** + * Request a stored SourceFile with a given fileName and compilationSettings. + * The first call to acquire will call createLanguageServiceSourceFile to generate + * the SourceFile if was not found in the registry. + * + * @param fileName The name of the file requested + * @param compilationSettings Some compilation settings like target affects the + * shape of a the resulting SourceFile. This allows the DocumentRegistry to store + * multiple copies of the same file for different compilation settings. + * @parm scriptSnapshot Text of the file. Only used if the file was not found + * in the registry and a new one was created. + * @parm version Current version of the file. Only used if the file was not found + * in the registry and a new one was created. + */ + acquireDocument( + fileName: string, + compilationSettings: CompilerOptions, + scriptSnapshot: IScriptSnapshot, + version: string, + scriptKind?: ScriptKind): SourceFile; + + acquireDocumentWithKey( + fileName: string, + path: Path, + compilationSettings: CompilerOptions, + key: DocumentRegistryBucketKey, + scriptSnapshot: IScriptSnapshot, + version: string, + scriptKind?: ScriptKind): SourceFile; + + /** + * Request an updated version of an already existing SourceFile with a given fileName + * and compilationSettings. The update will in-turn call updateLanguageServiceSourceFile + * to get an updated SourceFile. + * + * @param fileName The name of the file requested + * @param compilationSettings Some compilation settings like target affects the + * shape of a the resulting SourceFile. This allows the DocumentRegistry to store + * multiple copies of the same file for different compilation settings. + * @param scriptSnapshot Text of the file. + * @param version Current version of the file. + */ + updateDocument( + fileName: string, + compilationSettings: CompilerOptions, + scriptSnapshot: IScriptSnapshot, + version: string, + scriptKind?: ScriptKind): SourceFile; + + updateDocumentWithKey( + fileName: string, + path: Path, + compilationSettings: CompilerOptions, + key: DocumentRegistryBucketKey, + scriptSnapshot: IScriptSnapshot, + version: string, + scriptKind?: ScriptKind): SourceFile; + + getKeyForCompilationSettings(settings: CompilerOptions): DocumentRegistryBucketKey; + /** + * Informs the DocumentRegistry that a file is not needed any longer. + * + * Note: It is not allowed to call release on a SourceFile that was not acquired from + * this registry originally. + * + * @param fileName The name of the file to be released + * @param compilationSettings The compilation settings used to acquire the file + */ + releaseDocument(fileName: string, compilationSettings: CompilerOptions): void; + + releaseDocumentWithKey(path: Path, key: DocumentRegistryBucketKey): void; + + reportStats(): string; + } + + export type DocumentRegistryBucketKey = string & { __bucketKey: any }; + + interface DocumentRegistryEntry { + sourceFile: SourceFile; + + // The number of language services that this source file is referenced in. When no more + // language services are referencing the file, then the file can be removed from the + // registry. + languageServiceRefCount: number; + owners: string[]; + } + + export function createDocumentRegistry(useCaseSensitiveFileNames?: boolean, currentDirectory = ""): DocumentRegistry { + // Maps from compiler setting target (ES3, ES5, etc.) to all the cached documents we have + // for those settings. + const buckets = createMap>(); + const getCanonicalFileName = createGetCanonicalFileName(!!useCaseSensitiveFileNames); + + function getKeyForCompilationSettings(settings: CompilerOptions): DocumentRegistryBucketKey { + return `_${settings.target}|${settings.module}|${settings.noResolve}|${settings.jsx}|${settings.allowJs}|${settings.baseUrl}|${JSON.stringify(settings.typeRoots)}|${JSON.stringify(settings.rootDirs)}|${JSON.stringify(settings.paths)}`; + } + + function getBucketForCompilationSettings(key: DocumentRegistryBucketKey, createIfMissing: boolean): FileMap { + let bucket = buckets[key]; + if (!bucket && createIfMissing) { + buckets[key] = bucket = createFileMap(); + } + return bucket; + } + + function reportStats() { + const bucketInfoArray = Object.keys(buckets).filter(name => name && name.charAt(0) === "_").map(name => { + const entries = buckets[name]; + const sourceFiles: { name: string; refCount: number; references: string[]; }[] = []; + entries.forEachValue((key, entry) => { + sourceFiles.push({ + name: key, + refCount: entry.languageServiceRefCount, + references: entry.owners.slice(0) + }); + }); + sourceFiles.sort((x, y) => y.refCount - x.refCount); + return { + bucket: name, + sourceFiles + }; + }); + return JSON.stringify(bucketInfoArray, undefined, 2); + } + + function acquireDocument(fileName: string, compilationSettings: CompilerOptions, scriptSnapshot: IScriptSnapshot, version: string, scriptKind?: ScriptKind): SourceFile { + const path = toPath(fileName, currentDirectory, getCanonicalFileName); + const key = getKeyForCompilationSettings(compilationSettings); + return acquireDocumentWithKey(fileName, path, compilationSettings, key, scriptSnapshot, version, scriptKind); + } + + function acquireDocumentWithKey(fileName: string, path: Path, compilationSettings: CompilerOptions, key: DocumentRegistryBucketKey, scriptSnapshot: IScriptSnapshot, version: string, scriptKind?: ScriptKind): SourceFile { + return acquireOrUpdateDocument(fileName, path, compilationSettings, key, scriptSnapshot, version, /*acquiring*/ true, scriptKind); + } + + function updateDocument(fileName: string, compilationSettings: CompilerOptions, scriptSnapshot: IScriptSnapshot, version: string, scriptKind?: ScriptKind): SourceFile { + const path = toPath(fileName, currentDirectory, getCanonicalFileName); + const key = getKeyForCompilationSettings(compilationSettings); + return updateDocumentWithKey(fileName, path, compilationSettings, key, scriptSnapshot, version, scriptKind); + } + + function updateDocumentWithKey(fileName: string, path: Path, compilationSettings: CompilerOptions, key: DocumentRegistryBucketKey, scriptSnapshot: IScriptSnapshot, version: string, scriptKind?: ScriptKind): SourceFile { + return acquireOrUpdateDocument(fileName, path, compilationSettings, key, scriptSnapshot, version, /*acquiring*/ false, scriptKind); + } + + function acquireOrUpdateDocument( + fileName: string, + path: Path, + compilationSettings: CompilerOptions, + key: DocumentRegistryBucketKey, + scriptSnapshot: IScriptSnapshot, + version: string, + acquiring: boolean, + scriptKind?: ScriptKind): SourceFile { + + const bucket = getBucketForCompilationSettings(key, /*createIfMissing*/ true); + let entry = bucket.get(path); + if (!entry) { + Debug.assert(acquiring, "How could we be trying to update a document that the registry doesn't have?"); + + // Have never seen this file with these settings. Create a new source file for it. + const sourceFile = createLanguageServiceSourceFile(fileName, scriptSnapshot, compilationSettings.target, version, /*setNodeParents*/ false, scriptKind); + + entry = { + sourceFile: sourceFile, + languageServiceRefCount: 0, + owners: [] + }; + bucket.set(path, entry); + } + else { + // We have an entry for this file. However, it may be for a different version of + // the script snapshot. If so, update it appropriately. Otherwise, we can just + // return it as is. + if (entry.sourceFile.version !== version) { + entry.sourceFile = updateLanguageServiceSourceFile(entry.sourceFile, scriptSnapshot, version, + scriptSnapshot.getChangeRange(entry.sourceFile.scriptSnapshot)); + } + } + + // If we're acquiring, then this is the first time this LS is asking for this document. + // Increase our ref count so we know there's another LS using the document. If we're + // not acquiring, then that means the LS is 'updating' the file instead, and that means + // it has already acquired the document previously. As such, we do not need to increase + // the ref count. + if (acquiring) { + entry.languageServiceRefCount++; + } + + return entry.sourceFile; + } + + function releaseDocument(fileName: string, compilationSettings: CompilerOptions): void { + const path = toPath(fileName, currentDirectory, getCanonicalFileName); + const key = getKeyForCompilationSettings(compilationSettings); + return releaseDocumentWithKey(path, key); + } + + function releaseDocumentWithKey(path: Path, key: DocumentRegistryBucketKey): void { + const bucket = getBucketForCompilationSettings(key, /*createIfMissing*/false); + Debug.assert(bucket !== undefined); + + const entry = bucket.get(path); + entry.languageServiceRefCount--; + + Debug.assert(entry.languageServiceRefCount >= 0); + if (entry.languageServiceRefCount === 0) { + bucket.remove(path); + } + } + + return { + acquireDocument, + acquireDocumentWithKey, + updateDocument, + updateDocumentWithKey, + releaseDocument, + releaseDocumentWithKey, + reportStats, + getKeyForCompilationSettings + }; + } +}