mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-12 12:57:11 -06:00
241 lines
12 KiB
TypeScript
241 lines
12 KiB
TypeScript
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<FileMap<DocumentRegistryEntry>>();
|
|
const getCanonicalFileName = createGetCanonicalFileName(!!useCaseSensitiveFileNames);
|
|
|
|
function getKeyForCompilationSettings(settings: CompilerOptions): DocumentRegistryBucketKey {
|
|
return <DocumentRegistryBucketKey>`_${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<DocumentRegistryEntry> {
|
|
let bucket = buckets[key];
|
|
if (!bucket && createIfMissing) {
|
|
buckets[key] = bucket = createFileMap<DocumentRegistryEntry>();
|
|
}
|
|
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
|
|
};
|
|
}
|
|
}
|