Skip to content

Commit 69645af

Browse files
committed
Added DocumentBuilder option to skip eager linking
1 parent efb6c9e commit 69645af

File tree

3 files changed

+70
-27
lines changed

3 files changed

+70
-27
lines changed

packages/langium/src/validation/document-validator.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ export interface ValidationOptions {
2828
categories?: ValidationCategory[];
2929
/** If true, no further diagnostics are reported if there are lexing errors. */
3030
stopAfterLexingErrors?: boolean
31-
/** If true, no further diagnostics are reported if there are parsing errors. */
31+
/** If true, no further diagnostics are reported if there are parsing errors. Lexing errors are reported first. */
3232
stopAfterParsingErrors?: boolean
33-
/** If true, no further diagnostics are reported if there are linking errors. */
33+
/** If true, no further diagnostics are reported if there are linking errors. Lexing and parsing errors are reported first. */
3434
stopAfterLinkingErrors?: boolean
3535
}
3636

packages/langium/src/workspace/document-builder.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@ import { ValidationCategory } from '../validation/validation-registry.js';
2222
import { DocumentState } from './documents.js';
2323

2424
export interface BuildOptions {
25+
/**
26+
* Control the linking and references indexing phase with this option. The default if not specified is `true`.
27+
* If set to `false`, references can still be resolved - that's done lazily when you access the `ref` property of
28+
* a reference. But you won't get any diagnostics for linking errors and the references won't be considered
29+
* when updating other documents.
30+
*/
31+
eagerLinking?: boolean
32+
2533
/**
2634
* Control the validation phase with this option:
2735
* - `true` enables all validation checks and forces revalidating the documents
@@ -322,12 +330,13 @@ export class DefaultDocumentBuilder implements DocumentBuilder {
322330
doc.precomputedScopes = await scopeComputation.computeLocalScopes(doc, cancelToken);
323331
});
324332
// 3. Linking
325-
await this.runCancelable(documents, DocumentState.Linked, cancelToken, doc => {
333+
const toBeLinked = documents.filter(doc => this.shouldLink(doc));
334+
await this.runCancelable(toBeLinked, DocumentState.Linked, cancelToken, doc => {
326335
const linker = this.serviceRegistry.getServices(doc.uri).references.Linker;
327336
return linker.link(doc, cancelToken);
328337
});
329338
// 4. Index references
330-
await this.runCancelable(documents, DocumentState.IndexedReferences, cancelToken, doc =>
339+
await this.runCancelable(toBeLinked, DocumentState.IndexedReferences, cancelToken, doc =>
331340
this.indexManager.updateReferences(doc, cancelToken)
332341
);
333342
// 5. Validation
@@ -479,6 +488,16 @@ export class DefaultDocumentBuilder implements DocumentBuilder {
479488
}
480489
}
481490

491+
/**
492+
* Determine whether the given document should be linked during a build. The default
493+
* implementation checks the `eagerLinking` property of the build options. If it's set to `true`
494+
* or `undefined`, the document is included in the linking phase. This also affects the
495+
* references indexing phase, which depends on eager linking.
496+
*/
497+
protected shouldLink(document: LangiumDocument): boolean {
498+
return this.getBuildOptions(document).eagerLinking ?? true;
499+
}
500+
482501
/**
483502
* Determine whether the given document should be validated during a build. The default
484503
* implementation checks the `validation` property of the build options. If it's set to `true`

packages/langium/test/workspace/document-builder.test.ts

Lines changed: 47 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ describe('DefaultDocumentBuilder', () => {
5454
const workspace = services.shared.workspace;
5555
const documentFactory = workspace.LangiumDocumentFactory;
5656
const documents = workspace.LangiumDocuments;
57-
const document = documentFactory.fromString('', URI.parse('file:///test1.txt'));
57+
const document = documentFactory.fromString<Model>('', URI.parse('file:///test1.txt'));
5858
documents.addDocument(document);
5959

6060
const builder = workspace.DocumentBuilder;
@@ -73,7 +73,7 @@ describe('DefaultDocumentBuilder', () => {
7373
const workspace = services.shared.workspace;
7474
const documentFactory = workspace.LangiumDocumentFactory;
7575
const documents = workspace.LangiumDocuments;
76-
const document = documentFactory.fromString('', URI.parse('file:///test1.txt'));
76+
const document = documentFactory.fromString<Model>('', URI.parse('file:///test1.txt'));
7777
documents.addDocument(document);
7878

7979
const builder = workspace.DocumentBuilder;
@@ -91,7 +91,7 @@ describe('DefaultDocumentBuilder', () => {
9191
const services = await createServices();
9292
const documentFactory = services.shared.workspace.LangiumDocumentFactory;
9393
const documents = services.shared.workspace.LangiumDocuments;
94-
const document = documentFactory.fromString(`
94+
const document = documentFactory.fromString<Model>(`
9595
foo 1 A
9696
foo 11 B
9797
bar A
@@ -131,14 +131,14 @@ describe('DefaultDocumentBuilder', () => {
131131
const workspace = services.shared.workspace;
132132
const documentFactory = workspace.LangiumDocumentFactory;
133133
const documents = workspace.LangiumDocuments;
134-
const document1 = documentFactory.fromString(`
134+
const document1 = documentFactory.fromString<Model>(`
135135
foo 1 A
136136
foo 11 B
137137
bar A
138138
bar B
139139
`, URI.parse('file:///test1.txt'));
140140
documents.addDocument(document1);
141-
const document2 = documentFactory.fromString(`
141+
const document2 = documentFactory.fromString<Model>(`
142142
foo 1 C
143143
foo 11 D
144144
bar C
@@ -176,14 +176,14 @@ describe('DefaultDocumentBuilder', () => {
176176
const workspace = services.shared.workspace;
177177
const documentFactory = workspace.LangiumDocumentFactory;
178178
const documents = workspace.LangiumDocuments;
179-
const document1 = documentFactory.fromString(`
179+
const document1 = documentFactory.fromString<Model>(`
180180
foo 1 A
181181
foo 11 B
182182
bar A
183183
bar B
184184
`, URI.parse('file:///test1.txt'));
185185
documents.addDocument(document1);
186-
const document2 = documentFactory.fromString(`
186+
const document2 = documentFactory.fromString<Model>(`
187187
foo 1 C
188188
foo 11 A
189189
bar C
@@ -225,7 +225,7 @@ describe('DefaultDocumentBuilder', () => {
225225
const services = await createServices();
226226
const documentFactory = services.shared.workspace.LangiumDocumentFactory;
227227
const documents = services.shared.workspace.LangiumDocuments;
228-
const document1 = documentFactory.fromString(`
228+
const document1 = documentFactory.fromString<Model>(`
229229
foo 1 AnotherStrangeBar
230230
foo 11 B
231231
bar AnotherStrangeBar
@@ -260,7 +260,7 @@ describe('DefaultDocumentBuilder', () => {
260260
const services = await createServices();
261261
const documentFactory = services.shared.workspace.LangiumDocumentFactory;
262262
const documents = services.shared.workspace.LangiumDocuments;
263-
const document1 = documentFactory.fromString(`
263+
const document1 = documentFactory.fromString<Model>(`
264264
foo 1 AnotherStrangeBar
265265
foo 11 B
266266
bar AnotherStrangeBar
@@ -283,13 +283,37 @@ describe('DefaultDocumentBuilder', () => {
283283
]);
284284
});
285285

286+
test('skips linking if eagerLinking is false', async () => {
287+
const services = await createServices();
288+
const documentFactory = services.shared.workspace.LangiumDocumentFactory;
289+
const documents = services.shared.workspace.LangiumDocuments;
290+
const document = documentFactory.fromString<Model>(`
291+
foo 1 A
292+
foo 11 B
293+
bar A
294+
bar B
295+
`, URI.parse('file:///test1.txt'));
296+
documents.addDocument(document);
297+
298+
const builder = services.shared.workspace.DocumentBuilder;
299+
await builder.build([document], { eagerLinking: false });
300+
expect(document.state).toBe(DocumentState.ComputedScopes);
301+
// References should not be linked when eagerLinking is false
302+
expect(document.references).toHaveLength(0);
303+
// But we can still resolve references on demand
304+
const firstFoo = document.parseResult.value.foos[0];
305+
expect(firstFoo.bar.ref).toBeDefined();
306+
expect(firstFoo.bar.ref!.$type).toBe('Bar');
307+
expect(firstFoo.bar.ref!.name).toBe('A');
308+
});
309+
286310
test('can handle multiple listeners (buildPhase)', async () => {
287311
const services = await createServices();
288312
const workspace = services.shared.workspace;
289313
const documentFactory = workspace.LangiumDocumentFactory;
290314
const documents = workspace.LangiumDocuments;
291315
const uri = URI.parse('file:///test1.txt');
292-
const document1 = documentFactory.fromString(`
316+
const document1 = documentFactory.fromString<Model>(`
293317
foo 1 A
294318
foo 11 B
295319
bar A
@@ -313,7 +337,7 @@ describe('DefaultDocumentBuilder', () => {
313337
const documentFactory = workspace.LangiumDocumentFactory;
314338
const documents = workspace.LangiumDocuments;
315339
const uri = URI.parse('file:///test1.txt');
316-
const document1 = documentFactory.fromString(`
340+
const document1 = documentFactory.fromString<Model>(`
317341
foo 1 A
318342
foo 11 B
319343
bar A
@@ -342,7 +366,7 @@ describe('DefaultDocumentBuilder', () => {
342366
const documentFactory = services.shared.workspace.LangiumDocumentFactory;
343367
const documents = services.shared.workspace.LangiumDocuments;
344368
const builder = services.shared.workspace.DocumentBuilder;
345-
const document = documentFactory.fromString('', URI.parse('file:///test1.txt'));
369+
const document = documentFactory.fromString<Model>('', URI.parse('file:///test1.txt'));
346370
documents.addDocument(document);
347371

348372
const actual: string[] = [];
@@ -366,7 +390,7 @@ describe('DefaultDocumentBuilder', () => {
366390
const documentFactory = services.shared.workspace.LangiumDocumentFactory;
367391
const documents = services.shared.workspace.LangiumDocuments;
368392
const builder = services.shared.workspace.DocumentBuilder;
369-
const document = documentFactory.fromString('', URI.parse('file:///test1.txt'));
393+
const document = documentFactory.fromString<Model>('', URI.parse('file:///test1.txt'));
370394
documents.addDocument(document);
371395

372396
const actual: string[] = [];
@@ -402,7 +426,7 @@ describe('DefaultDocumentBuilder', () => {
402426
const documentFactory = services.shared.workspace.LangiumDocumentFactory;
403427
const documents = services.shared.workspace.LangiumDocuments;
404428
const builder = services.shared.workspace.DocumentBuilder;
405-
const document = documentFactory.fromString('', URI.parse('file:///test1.txt'));
429+
const document = documentFactory.fromString<Model>('', URI.parse('file:///test1.txt'));
406430
documents.addDocument(document);
407431

408432
const cancelTokenSource = startCancelableOperation();
@@ -423,7 +447,7 @@ describe('DefaultDocumentBuilder', () => {
423447
const documents = services.shared.workspace.LangiumDocuments;
424448
const builder = services.shared.workspace.DocumentBuilder;
425449
const documentUri = URI.parse('file:///test1.txt');
426-
const document = documentFactory.fromString('', documentUri);
450+
const document = documentFactory.fromString<Model>('', documentUri);
427451
documents.addDocument(document);
428452
await builder.build([document], { validation: true });
429453
expect(document.state).toBe(DocumentState.Validated);
@@ -440,7 +464,7 @@ describe('DefaultDocumentBuilder', () => {
440464
const services = await createServices();
441465
const documentFactory = services.shared.workspace.LangiumDocumentFactory;
442466
const documents = services.shared.workspace.LangiumDocuments;
443-
const document = documentFactory.fromString(`
467+
const document = documentFactory.fromString<Model>(`
444468
foo 1 A
445469
foo 11 B
446470
bar A
@@ -470,14 +494,14 @@ describe('DefaultDocumentBuilder', () => {
470494
const services = await createServices();
471495
const documentFactory = services.shared.workspace.LangiumDocumentFactory;
472496
const documents = services.shared.workspace.LangiumDocuments;
473-
const document1 = documentFactory.fromString(`
497+
const document1 = documentFactory.fromString<Model>(`
474498
foo 1 A
475499
foo 11 B
476500
bar A
477501
bar B
478502
`, URI.parse('file:///test1.txt'));
479503
documents.addDocument(document1);
480-
const document2 = documentFactory.fromString(`
504+
const document2 = documentFactory.fromString<Model>(`
481505
foo 1 A
482506
foo 11 B
483507
bar A
@@ -521,7 +545,7 @@ describe('DefaultDocumentBuilder', () => {
521545
const workspace = services.shared.workspace;
522546
const documentFactory = workspace.LangiumDocumentFactory;
523547
const documents = workspace.LangiumDocuments;
524-
const document = documentFactory.fromString(`
548+
const document = documentFactory.fromString<Model>(`
525549
foo 1 A
526550
foo 11 B
527551
bar A
@@ -558,7 +582,7 @@ describe('DefaultDocumentBuilder', () => {
558582
const workspace = services.shared.workspace;
559583
const documentFactory = workspace.LangiumDocumentFactory;
560584
const documents = workspace.LangiumDocuments;
561-
const document = documentFactory.fromString(`
585+
const document = documentFactory.fromString<Model>(`
562586
foo 1 A
563587
foo 11 B
564588
bar A
@@ -582,10 +606,9 @@ describe('DefaultDocumentBuilder', () => {
582606

583607
// Resolve the reference "on-the-fly"
584608
// We would expect that doing so will add the reference to the document references
585-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
586-
const first = (document.parseResult.value as any).foos[0].bar.ref;
609+
const first = document.parseResult.value.foos[0].bar.ref;
587610
expect(first).toBeDefined();
588-
expect(first.$type).toBe('Bar');
611+
expect(first!.$type).toBe('Bar');
589612

590613
expect(document.references).toHaveLength(1);
591614

@@ -747,6 +770,7 @@ type TestAstType = {
747770

748771
interface Model extends AstNode {
749772
foos: Foo[]
773+
bars: Bar[]
750774
}
751775

752776
interface Foo extends AstNode {

0 commit comments

Comments
 (0)