Skip to content

Commit dc18342

Browse files
authored
Enable cache eviction on specific document state (#1659)
1 parent d71b47b commit dc18342

File tree

2 files changed

+74
-12
lines changed

2 files changed

+74
-12
lines changed

packages/langium/src/utils/caching.ts

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import type { Disposable } from './disposable.js';
88
import type { URI } from './uri-utils.js';
99
import type { LangiumSharedCoreServices } from '../services.js';
10+
import type { DocumentState } from '../workspace/documents.js';
1011

1112
export abstract class DisposableCache implements Disposable {
1213

@@ -141,14 +142,35 @@ export class ContextCache<Context, Key, Value, ContextKey = Context> extends Dis
141142
* If this document is changed or deleted, all associated key/value pairs are deleted.
142143
*/
143144
export class DocumentCache<K, V> extends ContextCache<URI | string, K, V, string> {
144-
constructor(sharedServices: LangiumSharedCoreServices) {
145+
146+
/**
147+
* Creates a new document cache.
148+
*
149+
* @param sharedServices Service container instance to hook into document lifecycle events.
150+
* @param state Optional document state on which the cache should evict.
151+
* If not provided, the cache will evict on `DocumentBuilder#onUpdate`.
152+
* Note that only *changed* documents are considered in this case.
153+
*
154+
* Providing a state here will use `DocumentBuilder#onDocumentPhase` instead,
155+
* which triggers on all documents that have been affected by this change, assuming that the
156+
* state is `DocumentState.Linked` or a later state.
157+
*/
158+
constructor(sharedServices: LangiumSharedCoreServices, state?: DocumentState) {
145159
super(uri => uri.toString());
146-
this.onDispose(sharedServices.workspace.DocumentBuilder.onUpdate((changed, deleted) => {
147-
const allUris = changed.concat(deleted);
148-
for (const uri of allUris) {
149-
this.clear(uri);
150-
}
151-
}));
160+
let disposable: Disposable;
161+
if (state) {
162+
disposable = sharedServices.workspace.DocumentBuilder.onDocumentPhase(state, document => {
163+
this.clear(document.uri.toString());
164+
});
165+
} else {
166+
disposable = sharedServices.workspace.DocumentBuilder.onUpdate((changed, deleted) => {
167+
const allUris = changed.concat(deleted);
168+
for (const uri of allUris) {
169+
this.clear(uri);
170+
}
171+
});
172+
}
173+
this.toDispose.push(disposable);
152174
}
153175
}
154176

@@ -157,10 +179,26 @@ export class DocumentCache<K, V> extends ContextCache<URI | string, K, V, string
157179
* If any document in the workspace changes, the whole cache is evicted.
158180
*/
159181
export class WorkspaceCache<K, V> extends SimpleCache<K, V> {
160-
constructor(sharedServices: LangiumSharedCoreServices) {
182+
183+
/**
184+
* Creates a new workspace cache.
185+
*
186+
* @param sharedServices Service container instance to hook into document lifecycle events.
187+
* @param state Optional document state on which the cache should evict.
188+
* If not provided, the cache will evict on `DocumentBuilder#onUpdate`.
189+
*/
190+
constructor(sharedServices: LangiumSharedCoreServices, state?: DocumentState) {
161191
super();
162-
this.onDispose(sharedServices.workspace.DocumentBuilder.onUpdate(() => {
163-
this.clear();
164-
}));
192+
let disposable: Disposable;
193+
if (state) {
194+
disposable = sharedServices.workspace.DocumentBuilder.onBuildPhase(state, () => {
195+
this.clear();
196+
});
197+
} else {
198+
disposable = sharedServices.workspace.DocumentBuilder.onUpdate(() => {
199+
this.clear();
200+
});
201+
}
202+
this.toDispose.push(disposable);
165203
}
166204
}

packages/langium/test/utils/caching.test.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import { beforeEach, describe, expect, test } from 'vitest';
1010
import type { DefaultDocumentBuilder} from 'langium';
11-
import { DocumentCache, EmptyFileSystem, URI, WorkspaceCache } from 'langium';
11+
import { DocumentCache, DocumentState, EmptyFileSystem, URI, WorkspaceCache } from 'langium';
1212
import { createLangiumGrammarServices } from 'langium/grammar';
1313

1414
const services = createLangiumGrammarServices(EmptyFileSystem);
@@ -83,6 +83,18 @@ describe('Document Cache', () => {
8383
expect(() => cache.clear()).toThrow();
8484
});
8585

86+
test('Document cache can trigger on document state', async () => {
87+
const cache = new DocumentCache<string, string>(services.shared, DocumentState.Linked);
88+
cache.set(document1.uri, 'key', 'value');
89+
const documentPhase = workspace.DocumentBuilder.onDocumentPhase(DocumentState.ComputedScopes, () => {
90+
expect(cache.get(document1.uri, 'key')).toBe('value');
91+
});
92+
await workspace.DocumentBuilder.update([document1.uri], []);
93+
expect(cache.has(document1.uri, 'key')).toBe(false);
94+
documentPhase.dispose();
95+
cache.dispose();
96+
});
97+
8698
});
8799

88100
describe('Workspace Cache', () => {
@@ -129,4 +141,16 @@ describe('Workspace Cache', () => {
129141
expect(() => cache.delete('key')).toThrow();
130142
expect(() => cache.clear()).toThrow();
131143
});
144+
145+
test('Workspace cache can trigger on document state', async () => {
146+
const cache = new WorkspaceCache<string, string>(services.shared, DocumentState.Linked);
147+
cache.set('key', 'value');
148+
const documentPhase = workspace.DocumentBuilder.onBuildPhase(DocumentState.ComputedScopes, () => {
149+
expect(cache.get('key')).toBe('value');
150+
});
151+
await workspace.DocumentBuilder.update([document1.uri], []);
152+
expect(cache.has('key')).toBe(false);
153+
documentPhase.dispose();
154+
cache.dispose();
155+
});
132156
});

0 commit comments

Comments
 (0)