Skip to content

Commit 59f1bf8

Browse files
committed
Just cache ASTs only, do not reuse a registry
1 parent 92ba2d0 commit 59f1bf8

1 file changed

Lines changed: 67 additions & 48 deletions

File tree

src/harness/harnessLanguageService.ts

Lines changed: 67 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -133,85 +133,104 @@ function createLanguageServiceVfs(): vfs.FileSystem {
133133
return vfs.createFourslashVfs(IO, /*ignoreCase*/ true, { cwd: virtualFileSystemRoot });
134134
}
135135

136-
const sharedLibDocumentRegistry = ts.createDocumentRegistry(/*useCaseSensitiveFileNames*/ false, virtualFileSystemRoot);
136+
// Cache lib file SourceFiles directly to avoid reparsing across tests.
137+
// Lib .d.ts files parse identically regardless of compiler settings, so we cache
138+
// just one copy per path. This persists for the entire test run.
139+
const libSourceFileCache = new Map<string, ts.SourceFile>();
137140

138-
function createLibSharingDocumentRegistry(): ts.DocumentRegistry {
141+
function createLibCachingDocumentRegistry(): ts.DocumentRegistry {
139142
const localRegistry = ts.createDocumentRegistry(/*useCaseSensitiveFileNames*/ false, virtualFileSystemRoot);
140143

141-
const enableLibAstCaching = true;
142-
if (!enableLibAstCaching) {
143-
return localRegistry;
144-
}
145-
146-
const libFileVersions = new Map<string, string>();
147-
148144
function isLibFile(fileName: string): boolean {
149145
return vpath.beneath(vfs.fourslashLibFolder, fileName);
150146
}
151147

152-
function getRegistry(fileName: string): ts.DocumentRegistry {
153-
return isLibFile(fileName) ? sharedLibDocumentRegistry : localRegistry;
154-
}
155-
156-
function checkLibFileVersion(fileName: string, version: string): void {
157-
if (!isLibFile(fileName)) return;
158-
const existingVersion = libFileVersions.get(fileName);
159-
if (existingVersion === undefined) {
160-
libFileVersions.set(fileName, version);
161-
}
162-
else if (existingVersion !== version) {
163-
throw new Error(`Lib file "${fileName}" version changed from "${existingVersion}" to "${version}" - lib files should not be modified`);
164-
}
165-
}
166-
167148
return {
168149
acquireDocument(fileName, compilationSettingsOrHost, scriptSnapshot, version, scriptKind, sourceFileOptions) {
169-
checkLibFileVersion(fileName, version);
170-
return getRegistry(fileName).acquireDocument(fileName, compilationSettingsOrHost, scriptSnapshot, version, scriptKind, sourceFileOptions);
150+
if (isLibFile(fileName)) {
151+
const cached = libSourceFileCache.get(fileName);
152+
if (cached) {
153+
return cached;
154+
}
155+
// Not cached - acquire from local registry to parse it, then cache
156+
const sourceFile = localRegistry.acquireDocument(fileName, compilationSettingsOrHost, scriptSnapshot, version, scriptKind, sourceFileOptions);
157+
libSourceFileCache.set(fileName, sourceFile);
158+
// Release from local registry immediately - we don't need ref counting for cached libs
159+
const settings = typeof (compilationSettingsOrHost as ts.MinimalResolutionCacheHost).getCompilationSettings === "function"
160+
? (compilationSettingsOrHost as ts.MinimalResolutionCacheHost).getCompilationSettings()
161+
: compilationSettingsOrHost as ts.CompilerOptions;
162+
localRegistry.releaseDocument(fileName, settings, scriptKind!, typeof sourceFileOptions === "object" ? sourceFileOptions.impliedNodeFormat : undefined);
163+
return sourceFile;
164+
}
165+
return localRegistry.acquireDocument(fileName, compilationSettingsOrHost, scriptSnapshot, version, scriptKind, sourceFileOptions);
171166
},
172167
acquireDocumentWithKey(fileName, path, compilationSettingsOrHost, key, scriptSnapshot, version, scriptKind, sourceFileOptions) {
173-
checkLibFileVersion(fileName, version);
174-
return getRegistry(fileName).acquireDocumentWithKey(fileName, path, compilationSettingsOrHost, key, scriptSnapshot, version, scriptKind, sourceFileOptions);
168+
if (isLibFile(fileName)) {
169+
const cached = libSourceFileCache.get(fileName);
170+
if (cached) {
171+
return cached;
172+
}
173+
// Not cached - acquire from local registry to parse it, then cache
174+
const sourceFile = localRegistry.acquireDocumentWithKey(fileName, path, compilationSettingsOrHost, key, scriptSnapshot, version, scriptKind, sourceFileOptions);
175+
libSourceFileCache.set(fileName, sourceFile);
176+
// Release from local registry immediately
177+
const impliedNodeFormat = typeof sourceFileOptions === "object" ? sourceFileOptions.impliedNodeFormat : undefined;
178+
localRegistry.releaseDocumentWithKey(path, key, scriptKind!, impliedNodeFormat);
179+
return sourceFile;
180+
}
181+
return localRegistry.acquireDocumentWithKey(fileName, path, compilationSettingsOrHost, key, scriptSnapshot, version, scriptKind, sourceFileOptions);
175182
},
176183
updateDocument(fileName, compilationSettingsOrHost, scriptSnapshot, version, scriptKind, sourceFileOptions) {
177-
checkLibFileVersion(fileName, version);
178-
return getRegistry(fileName).updateDocument(fileName, compilationSettingsOrHost, scriptSnapshot, version, scriptKind, sourceFileOptions);
184+
if (isLibFile(fileName)) {
185+
// Lib files should never be updated - just return the cached version
186+
const cached = libSourceFileCache.get(fileName);
187+
if (cached) {
188+
return cached;
189+
}
190+
// Fall through to acquire if somehow not cached
191+
return this.acquireDocument(fileName, compilationSettingsOrHost, scriptSnapshot, version, scriptKind, sourceFileOptions);
192+
}
193+
return localRegistry.updateDocument(fileName, compilationSettingsOrHost, scriptSnapshot, version, scriptKind, sourceFileOptions);
179194
},
180195
updateDocumentWithKey(fileName, path, compilationSettingsOrHost, key, scriptSnapshot, version, scriptKind, sourceFileOptions) {
181-
checkLibFileVersion(fileName, version);
182-
return getRegistry(fileName).updateDocumentWithKey(fileName, path, compilationSettingsOrHost, key, scriptSnapshot, version, scriptKind, sourceFileOptions);
196+
if (isLibFile(fileName)) {
197+
// Lib files should never be updated - just return the cached version
198+
const cached = libSourceFileCache.get(fileName);
199+
if (cached) {
200+
return cached;
201+
}
202+
// Fall through to acquire if somehow not cached
203+
return this.acquireDocumentWithKey(fileName, path, compilationSettingsOrHost, key, scriptSnapshot, version, scriptKind, sourceFileOptions);
204+
}
205+
return localRegistry.updateDocumentWithKey(fileName, path, compilationSettingsOrHost, key, scriptSnapshot, version, scriptKind, sourceFileOptions);
183206
},
184207
getKeyForCompilationSettings(settings) {
185-
return sharedLibDocumentRegistry.getKeyForCompilationSettings(settings);
208+
return localRegistry.getKeyForCompilationSettings(settings);
186209
},
187210
getDocumentRegistryBucketKeyWithMode(key, mode) {
188-
return sharedLibDocumentRegistry.getDocumentRegistryBucketKeyWithMode(key, mode);
211+
return localRegistry.getDocumentRegistryBucketKeyWithMode(key, mode);
189212
},
190213
releaseDocument(fileName: string, compilationSettings: ts.CompilerOptions, scriptKind?: ts.ScriptKind, impliedNodeFormat?: ts.ResolutionMode) {
191-
// Need to handle the overloaded signature
192-
return getRegistry(fileName).releaseDocument(fileName, compilationSettings, scriptKind!, impliedNodeFormat);
214+
if (isLibFile(fileName)) {
215+
// Lib files are cached separately - no ref counting needed, so no-op
216+
return;
217+
}
218+
return localRegistry.releaseDocument(fileName, compilationSettings, scriptKind!, impliedNodeFormat);
193219
},
194220
releaseDocumentWithKey(path: ts.Path, key: ts.DocumentRegistryBucketKey, scriptKind?: ts.ScriptKind, impliedNodeFormat?: ts.ResolutionMode) {
195-
// We don't know the original file name here, so release from both registries
196-
// This is safe because releaseDocument is a no-op if the document wasn't acquired
221+
// We can't easily tell if this is a lib file from just the path without the fileName,
222+
// so try to release from local registry and ignore errors
197223
try {
198224
localRegistry.releaseDocumentWithKey(path, key, scriptKind!, impliedNodeFormat);
199225
}
200226
catch {
201-
// Ignore - document might be in the other registry
202-
}
203-
try {
204-
sharedLibDocumentRegistry.releaseDocumentWithKey(path, key, scriptKind!, impliedNodeFormat);
205-
}
206-
catch {
207-
// Ignore - document might be in the other registry
227+
// Ignore - might be a lib file that was never in the local registry
208228
}
209229
},
210230
reportStats() {
211-
return `Shared lib registry: ${sharedLibDocumentRegistry.reportStats()}\nLocal registry: ${localRegistry.reportStats()}`;
231+
return `Lib cache: ${libSourceFileCache.size} files\nLocal registry: ${localRegistry.reportStats()}`;
212232
},
213233
getBuckets() {
214-
// Return local buckets - this is primarily for debugging
215234
return localRegistry.getBuckets();
216235
},
217236
};
@@ -429,7 +448,7 @@ export class NativeLanguageServiceAdapter implements LanguageServiceAdapter {
429448
getLogger: typeof ts.returnUndefined = ts.returnUndefined;
430449
constructor(cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) {
431450
this.host = new NativeLanguageServiceHost(cancellationToken, options);
432-
this.documentRegistry = createLibSharingDocumentRegistry();
451+
this.documentRegistry = createLibCachingDocumentRegistry();
433452
}
434453
getHost(): LanguageServiceAdapterHost {
435454
return this.host;

0 commit comments

Comments
 (0)