@hydranium/core 1.0.0-next.95 → 1.0.0-next.96
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/langium/integrity/integrity-service.d.ts.map +1 -1
- package/lib/langium/integrity/integrity-service.js +8 -1
- package/lib/langium/integrity/integrity-service.js.map +1 -1
- package/lib/langium/language-module.d.ts +25 -0
- package/lib/langium/language-module.d.ts.map +1 -1
- package/lib/langium/language-module.js +14 -0
- package/lib/langium/language-module.js.map +1 -1
- package/lib/langium/model-service/model-service.d.ts +30 -18
- package/lib/langium/model-service/model-service.d.ts.map +1 -1
- package/lib/langium/model-service/model-service.js +41 -1
- package/lib/langium/model-service/model-service.js.map +1 -1
- package/lib/langium/residency/cst-residency-service.d.ts +4 -6
- package/lib/langium/residency/cst-residency-service.d.ts.map +1 -1
- package/lib/langium/residency/cst-residency-service.js.map +1 -1
- package/lib/langium/serialization/abstract-serializer.d.ts +11 -20
- package/lib/langium/serialization/abstract-serializer.d.ts.map +1 -1
- package/lib/langium/serialization/abstract-serializer.js +12 -21
- package/lib/langium/serialization/abstract-serializer.js.map +1 -1
- package/lib/langium/trivia/comment-preserver.d.ts +423 -0
- package/lib/langium/trivia/comment-preserver.d.ts.map +1 -0
- package/lib/langium/trivia/comment-preserver.js +906 -0
- package/lib/langium/trivia/comment-preserver.js.map +1 -0
- package/lib/langium/trivia/document-ending-preserver.d.ts +43 -0
- package/lib/langium/trivia/document-ending-preserver.d.ts.map +1 -0
- package/lib/langium/trivia/document-ending-preserver.js +48 -0
- package/lib/langium/trivia/document-ending-preserver.js.map +1 -0
- package/lib/langium/trivia/index.d.ts +14 -0
- package/lib/langium/trivia/index.d.ts.map +1 -0
- package/lib/langium/trivia/index.js +14 -0
- package/lib/langium/trivia/index.js.map +1 -0
- package/lib/langium/trivia/trivia-contribution.d.ts +37 -0
- package/lib/langium/trivia/trivia-contribution.d.ts.map +1 -0
- package/lib/langium/trivia/trivia-contribution.js +10 -0
- package/lib/langium/trivia/trivia-contribution.js.map +1 -0
- package/lib/langium/trivia/trivia-preserver.d.ts +50 -0
- package/lib/langium/trivia/trivia-preserver.d.ts.map +1 -0
- package/lib/langium/trivia/trivia-preserver.js +10 -0
- package/lib/langium/trivia/trivia-preserver.js.map +1 -0
- package/lib/langium/trivia/trivia-service.d.ts +70 -0
- package/lib/langium/trivia/trivia-service.d.ts.map +1 -0
- package/lib/langium/trivia/trivia-service.js +56 -0
- package/lib/langium/trivia/trivia-service.js.map +1 -0
- package/lib/testing/node/scratch-workspace.js +2 -2
- package/package.json +5 -5
- package/src/index.ts +1 -0
- package/src/langium/integrity/integrity-service.ts +9 -1
- package/src/langium/language-module.ts +38 -0
- package/src/langium/model-service/model-service.ts +58 -19
- package/src/langium/residency/cst-residency-service.ts +4 -6
- package/src/langium/serialization/abstract-serializer.ts +13 -32
- package/src/langium/trivia/comment-preserver.ts +1100 -0
- package/src/langium/trivia/document-ending-preserver.ts +55 -0
- package/src/langium/trivia/index.ts +14 -0
- package/src/langium/trivia/trivia-contribution.ts +39 -0
- package/src/langium/trivia/trivia-preserver.ts +54 -0
- package/src/langium/trivia/trivia-service.ts +99 -0
- package/src/testing/node/scratch-workspace.ts +2 -2
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (c) 2026 CrossBreeze, EclipseSource and others.
|
|
3
|
+
*
|
|
4
|
+
* This program and the accompanying materials are made available under the
|
|
5
|
+
* terms of the MIT License which is available in the project root.
|
|
6
|
+
*
|
|
7
|
+
* SPDX-License-Identifier: MIT
|
|
8
|
+
********************************************************************************/
|
|
9
|
+
|
|
10
|
+
import { type LangiumDocument } from '@hydranium/langium';
|
|
11
|
+
import { type TriviaContribution, type TriviaRegistry } from './trivia-contribution.js';
|
|
12
|
+
import { type TriviaPreserver } from './trivia-preserver.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Carries the whitespace a document ended with across a write.
|
|
16
|
+
*
|
|
17
|
+
* **Normalising this to one final newline rewrites files nobody asked to
|
|
18
|
+
* restyle.** A document may legitimately end in blank lines, or deliberately in
|
|
19
|
+
* none, and a write that changed one field is not the place to decide it should
|
|
20
|
+
* not. A serializer cannot make the call either — it has no prior file to
|
|
21
|
+
* consult — which is why the decision lives on the write path.
|
|
22
|
+
*
|
|
23
|
+
* Line terminators are matched to the body the serializer emits, which is the
|
|
24
|
+
* one thing here that cannot be preserved: keeping them verbatim leaves a
|
|
25
|
+
* rewritten CRLF document with its only CRLFs at the very end.
|
|
26
|
+
*/
|
|
27
|
+
export class DocumentEndingPreserver implements TriviaPreserver<string> {
|
|
28
|
+
readonly id = 'document-ending';
|
|
29
|
+
readonly label = 'Document Ending';
|
|
30
|
+
|
|
31
|
+
extract(document: LangiumDocument): string {
|
|
32
|
+
const source = document.textDocument.getText();
|
|
33
|
+
const ending = source.slice(source.trimEnd().length);
|
|
34
|
+
return ending.includes('\r') ? ending.replace(/\r\n/g, '\n') : ending;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* **Override for a grammar whose CONTENT can end in whitespace** — a
|
|
39
|
+
* block-scalar property as the document's last value, say. The trim cannot
|
|
40
|
+
* tell a blank line the serializer emitted as part of a value from one that
|
|
41
|
+
* is merely the end of the file, so it removes both and restores the ending
|
|
42
|
+
* the old document had. A document whose final value just gained trailing
|
|
43
|
+
* blank lines therefore loses them.
|
|
44
|
+
*/
|
|
45
|
+
apply(serialized: string, trivia: string): string {
|
|
46
|
+
return serialized.trimEnd() + trivia;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Registers {@link DocumentEndingPreserver}; bound at `trivia.preservers.documentEnding`. */
|
|
51
|
+
export class DocumentEndingPreserverContribution implements TriviaContribution {
|
|
52
|
+
registerTriviaPreservers(registry: TriviaRegistry): void {
|
|
53
|
+
registry.register(new DocumentEndingPreserver());
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (c) 2026 CrossBreeze, EclipseSource and others.
|
|
3
|
+
*
|
|
4
|
+
* This program and the accompanying materials are made available under the
|
|
5
|
+
* terms of the MIT License which is available in the project root.
|
|
6
|
+
*
|
|
7
|
+
* SPDX-License-Identifier: MIT
|
|
8
|
+
********************************************************************************/
|
|
9
|
+
|
|
10
|
+
export * from './comment-preserver.js';
|
|
11
|
+
export * from './document-ending-preserver.js';
|
|
12
|
+
export * from './trivia-contribution.js';
|
|
13
|
+
export * from './trivia-preserver.js';
|
|
14
|
+
export * from './trivia-service.js';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (c) 2026 CrossBreeze, EclipseSource and others.
|
|
3
|
+
*
|
|
4
|
+
* This program and the accompanying materials are made available under the
|
|
5
|
+
* terms of the MIT License which is available in the project root.
|
|
6
|
+
*
|
|
7
|
+
* SPDX-License-Identifier: MIT
|
|
8
|
+
********************************************************************************/
|
|
9
|
+
|
|
10
|
+
import { type Disposable } from 'vscode-languageserver';
|
|
11
|
+
import { type TriviaPreserver } from './trivia-preserver.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Registry handed to a {@link TriviaContribution}. Implemented by the trivia
|
|
15
|
+
* service; a contribution receives it and registers one or many preservers.
|
|
16
|
+
* Doubles as the low-level imperative API for the rare runtime-dynamic case.
|
|
17
|
+
*/
|
|
18
|
+
export interface TriviaRegistry {
|
|
19
|
+
register(preserver: TriviaPreserver): Disposable;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Declarative registration of trivia preservers. Bound under the module's
|
|
24
|
+
* `trivia.preservers` contribution group; the trivia service reads its own
|
|
25
|
+
* group at construction and calls this method, handing itself in as the
|
|
26
|
+
* registry.
|
|
27
|
+
*
|
|
28
|
+
* The framework binds one sub-key per preserver it ships rather than one for
|
|
29
|
+
* all of them, so an adopter replacing `comments` — a grammar identifying its
|
|
30
|
+
* nodes by something no name property holds — keeps the rest. Langium's
|
|
31
|
+
* deep-merge is last-wins on same-key leaves, so binding a sub-key REPLACES the
|
|
32
|
+
* framework's contribution under it rather than adding to it.
|
|
33
|
+
*
|
|
34
|
+
* The domain-qualified method name lets one cross-cutting class implement
|
|
35
|
+
* several contribution interfaces without method collision.
|
|
36
|
+
*/
|
|
37
|
+
export interface TriviaContribution {
|
|
38
|
+
registerTriviaPreservers(registry: TriviaRegistry): void;
|
|
39
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (c) 2026 CrossBreeze, EclipseSource and others.
|
|
3
|
+
*
|
|
4
|
+
* This program and the accompanying materials are made available under the
|
|
5
|
+
* terms of the MIT License which is available in the project root.
|
|
6
|
+
*
|
|
7
|
+
* SPDX-License-Identifier: MIT
|
|
8
|
+
********************************************************************************/
|
|
9
|
+
|
|
10
|
+
import { type LangiumDocument, type URI } from '@hydranium/langium';
|
|
11
|
+
import { type RegistryItem } from '../../util/registry.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* One kind of trivia — what a document carries that its model does not, so a
|
|
15
|
+
* write that re-serializes from the model would otherwise lose it.
|
|
16
|
+
*
|
|
17
|
+
* **Two phases, because there is a suspension point between them.** The write
|
|
18
|
+
* path extracts, awaits the serializer, then applies; and the preserver is a
|
|
19
|
+
* per-language singleton, so two concurrent writes would clobber each other if
|
|
20
|
+
* it held the extracted value itself. `T` therefore travels through the caller.
|
|
21
|
+
*
|
|
22
|
+
* Declared with METHODS rather than arrow properties: an implementation
|
|
23
|
+
* narrowing `T` stays assignable only under method-parameter bivariance, which
|
|
24
|
+
* arrow properties lose.
|
|
25
|
+
*/
|
|
26
|
+
export interface TriviaPreserver<T = unknown> extends RegistryItem {
|
|
27
|
+
/**
|
|
28
|
+
* Take this preserver's trivia off `document`, while it still holds the text
|
|
29
|
+
* the write is about to replace.
|
|
30
|
+
*/
|
|
31
|
+
extract(document: LangiumDocument): T;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Put `trivia` back into `serialized`, returning the text the NEXT preserver
|
|
35
|
+
* sees — the chain is a fold, so an implementation must derive its positions
|
|
36
|
+
* from the text it is handed rather than from the document it extracted from.
|
|
37
|
+
*/
|
|
38
|
+
apply(serialized: string, trivia: T, uri: URI): string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* One preserver's output, kept beside the preserver that produced it.
|
|
43
|
+
*
|
|
44
|
+
* Pairing them is what lets the chain stay agnostic: it never learns what a
|
|
45
|
+
* payload IS, only which preserver knows how to apply it, so a preserver can
|
|
46
|
+
* use whatever shape suits it without the service naming that shape.
|
|
47
|
+
*/
|
|
48
|
+
export interface ExtractedTrivia {
|
|
49
|
+
readonly preserver: TriviaPreserver;
|
|
50
|
+
readonly trivia: unknown;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Everything the registered preservers took off one document, in the order they ran. */
|
|
54
|
+
export type DocumentTrivia = readonly ExtractedTrivia[];
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (c) 2026 CrossBreeze, EclipseSource and others.
|
|
3
|
+
*
|
|
4
|
+
* This program and the accompanying materials are made available under the
|
|
5
|
+
* terms of the MIT License which is available in the project root.
|
|
6
|
+
*
|
|
7
|
+
* SPDX-License-Identifier: MIT
|
|
8
|
+
********************************************************************************/
|
|
9
|
+
|
|
10
|
+
import { type Tracer } from '@hydranium/protocol';
|
|
11
|
+
import { type LangiumDocument, type URI } from '@hydranium/langium';
|
|
12
|
+
import { type Disposable } from 'vscode-languageserver';
|
|
13
|
+
import { type LogNameOptions } from '../diagnostics/logger.js';
|
|
14
|
+
import { type HydraniumLanguageServices } from '../language-module.js';
|
|
15
|
+
import { Registry } from '../../util/registry.js';
|
|
16
|
+
import { type TriviaRegistry } from './trivia-contribution.js';
|
|
17
|
+
import { type DocumentTrivia, type TriviaPreserver } from './trivia-preserver.js';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Public contract for the per-language trivia runner. Extends the
|
|
21
|
+
* {@link TriviaRegistry} contributions register through with the entry points
|
|
22
|
+
* the write paths call around serialization.
|
|
23
|
+
*
|
|
24
|
+
* Adopter overrides go through {@link DefaultTriviaService}; the interface keeps
|
|
25
|
+
* the public API stable while the registry stays `protected` on the class.
|
|
26
|
+
*/
|
|
27
|
+
export interface TriviaService extends TriviaRegistry {
|
|
28
|
+
/** Remove a preserver by id. Returns `true` if one was removed. */
|
|
29
|
+
unregister(id: string): boolean;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Run every registered preserver's `extract` over `document`, in priority
|
|
33
|
+
* order, while it still holds the text the write is about to replace.
|
|
34
|
+
*/
|
|
35
|
+
extract(document: LangiumDocument): DocumentTrivia;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Fold `serialized` through every extracted entry, handing each preserver
|
|
39
|
+
* back its own payload, and return the text to write.
|
|
40
|
+
*/
|
|
41
|
+
apply(serialized: string, trivia: DocumentTrivia, uri: URI): string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Construction options for {@link DefaultTriviaService}. */
|
|
45
|
+
export type TriviaServiceOptions = LogNameOptions;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Default {@link TriviaService}: an ordered registry of preservers, run around
|
|
49
|
+
* the serializer on every structured write.
|
|
50
|
+
*
|
|
51
|
+
* **An empty registry is a no-op**, which is how preservation is switched off —
|
|
52
|
+
* there is no enabled flag, and a preserver that is not registered costs
|
|
53
|
+
* nothing rather than being asked and declining.
|
|
54
|
+
*
|
|
55
|
+
* **Per-language service.** Comment terminals come from the grammar, so this
|
|
56
|
+
* cannot be shared; `ModelService` resolves it via
|
|
57
|
+
* `ServiceRegistry.getServices(uri)` so a multi-grammar workspace routes each
|
|
58
|
+
* write to the right preservers.
|
|
59
|
+
*/
|
|
60
|
+
export class DefaultTriviaService implements TriviaService {
|
|
61
|
+
/** Registry of preservers, keyed by id, iterated in priority order. */
|
|
62
|
+
protected readonly preservers = new Registry<TriviaPreserver>();
|
|
63
|
+
protected readonly tracer: Tracer;
|
|
64
|
+
|
|
65
|
+
constructor(
|
|
66
|
+
protected readonly services: HydraniumLanguageServices,
|
|
67
|
+
options: TriviaServiceOptions = {}
|
|
68
|
+
) {
|
|
69
|
+
this.tracer = services.shared.Tracer.for(options.logName ?? 'Trivia').trace('instantiated');
|
|
70
|
+
|
|
71
|
+
// Optional chaining tolerates incomplete test stubs; production wiring
|
|
72
|
+
// always provides the slot via `createServerLanguageModule`.
|
|
73
|
+
const contributions = services.trivia?.preservers ?? {};
|
|
74
|
+
for (const contribution of Object.values(contributions)) {
|
|
75
|
+
contribution.registerTriviaPreservers(this);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
register(preserver: TriviaPreserver): Disposable {
|
|
80
|
+
return this.preservers.register(preserver);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
unregister(id: string): boolean {
|
|
84
|
+
return this.preservers.unregister(id);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
extract(document: LangiumDocument): DocumentTrivia {
|
|
88
|
+
return this.preservers.all().map(preserver => ({ preserver, trivia: preserver.extract(document) }));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Fold, not map: each preserver sees what the ones before it produced, so a
|
|
93
|
+
* pair that touch the same region resolve by priority rather than by
|
|
94
|
+
* whichever happened to register first.
|
|
95
|
+
*/
|
|
96
|
+
apply(serialized: string, trivia: DocumentTrivia, uri: URI): string {
|
|
97
|
+
return trivia.reduce((text, entry) => entry.preserver.apply(text, entry.trivia, uri), serialized);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
* committed sample workspace can **rewrite the sample files**, after which
|
|
22
22
|
* every later run asserts against already-repaired input: a suite that passes
|
|
23
23
|
* while testing nothing. The same applies to a fixture built in place, which
|
|
24
|
-
* additionally
|
|
25
|
-
* through
|
|
24
|
+
* additionally comes back in the serializer's layout, because the write-back
|
|
25
|
+
* goes through it.
|
|
26
26
|
*
|
|
27
27
|
* The second reason is cleanup. Hand-rolled `mkdtempSync` without a matching
|
|
28
28
|
* `rmSync` leaks a directory per test run into the OS temp dir, which nobody
|