@hydranium/core 1.0.0-next.282 → 1.0.0-next.284

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.
Files changed (45) hide show
  1. package/lib/documents/ast-document-manager.d.ts +5 -3
  2. package/lib/documents/ast-document-manager.d.ts.map +1 -1
  3. package/lib/documents/ast-document-manager.js +3 -2
  4. package/lib/documents/ast-document-manager.js.map +1 -1
  5. package/lib/langium/model-service/client-session.d.ts +5 -3
  6. package/lib/langium/model-service/client-session.d.ts.map +1 -1
  7. package/lib/langium/model-service/client-session.js.map +1 -1
  8. package/lib/langium/model-service/model-service.d.ts +12 -2
  9. package/lib/langium/model-service/model-service.d.ts.map +1 -1
  10. package/lib/langium/model-service/model-service.js +12 -5
  11. package/lib/langium/model-service/model-service.js.map +1 -1
  12. package/lib/langium/naming/name-provider.d.ts +15 -1
  13. package/lib/langium/naming/name-provider.d.ts.map +1 -1
  14. package/lib/langium/naming/name-provider.js +6 -1
  15. package/lib/langium/naming/name-provider.js.map +1 -1
  16. package/lib/langium/trivia/document-ending-preserver.d.ts +11 -3
  17. package/lib/langium/trivia/document-ending-preserver.d.ts.map +1 -1
  18. package/lib/langium/trivia/document-ending-preserver.js +20 -6
  19. package/lib/langium/trivia/document-ending-preserver.js.map +1 -1
  20. package/lib/langium/trivia/trivia-preserver.d.ts +3 -1
  21. package/lib/langium/trivia/trivia-preserver.d.ts.map +1 -1
  22. package/lib/langium/workspace/hydranium-workspace-manager.d.ts +11 -10
  23. package/lib/langium/workspace/hydranium-workspace-manager.d.ts.map +1 -1
  24. package/lib/langium/workspace/hydranium-workspace-manager.js +13 -13
  25. package/lib/langium/workspace/hydranium-workspace-manager.js.map +1 -1
  26. package/lib/langium/workspace/virtual-document.d.ts +6 -6
  27. package/lib/langium/workspace/virtual-document.js +6 -6
  28. package/lib/testing/node/lsp-server-connection.d.ts +7 -1
  29. package/lib/testing/node/lsp-server-connection.d.ts.map +1 -1
  30. package/lib/testing/node/lsp-server-connection.js +4 -0
  31. package/lib/testing/node/lsp-server-connection.js.map +1 -1
  32. package/lib/testing/run-update-pipeline.d.ts +4 -2
  33. package/lib/testing/run-update-pipeline.d.ts.map +1 -1
  34. package/lib/testing/run-update-pipeline.js.map +1 -1
  35. package/package.json +5 -5
  36. package/src/documents/ast-document-manager.ts +5 -3
  37. package/src/langium/model-service/client-session.ts +5 -3
  38. package/src/langium/model-service/model-service.ts +16 -6
  39. package/src/langium/naming/name-provider.ts +15 -1
  40. package/src/langium/trivia/document-ending-preserver.ts +22 -6
  41. package/src/langium/trivia/trivia-preserver.ts +3 -1
  42. package/src/langium/workspace/hydranium-workspace-manager.ts +13 -13
  43. package/src/langium/workspace/virtual-document.ts +6 -6
  44. package/src/testing/node/lsp-server-connection.ts +7 -1
  45. package/src/testing/run-update-pipeline.ts +4 -2
@@ -221,6 +221,11 @@ export interface NameProvider extends LangiumNameProvider {
221
221
  * walk traverses through the document root: if the root has its own
222
222
  * name, that name IS part of the qualified form. Adopters who want a
223
223
  * different boundary override this method.
224
+ *
225
+ * May answer as of its first read since the last build: the default
226
+ * memoises per built node, so a name changed in memory after that read
227
+ * reads as the old one until its document rebuilds. A node in no document
228
+ * is named afresh on every call.
224
229
  */
225
230
  getDocumentQualifiedName(node?: AstNode): string | undefined;
226
231
 
@@ -236,6 +241,10 @@ export interface NameProvider extends LangiumNameProvider {
236
241
  * string) or no project owns the URI — in those cases project-
237
242
  * qualified equals document-qualified, and
238
243
  * `HydraniumScopeComputation` skips the multi-tier emit.
244
+ *
245
+ * The default memoises it as it does {@link getDocumentQualifiedName}, and
246
+ * for a node in no document answers the document-qualified name: there is
247
+ * no project to prefix.
239
248
  */
240
249
  getProjectQualifiedName(node?: AstNode): string | undefined;
241
250
 
@@ -326,6 +335,9 @@ export class DefaultNameProvider implements NameProvider {
326
335
  * document's lifetime. Evicted automatically when Langium rebuilds
327
336
  * the document.
328
337
  *
338
+ * So a built node renamed in memory reads its old name until the rebuild,
339
+ * as {@link NameProvider.getDocumentQualifiedName} warns.
340
+ *
329
341
  * Only cached when the computed result is a defined string — `undefined`
330
342
  * results re-evaluate on each call (cheap path: `getOwnName` returns
331
343
  * undefined for unnamed nodes, no chain walk needed).
@@ -334,7 +346,9 @@ export class DefaultNameProvider implements NameProvider {
334
346
  /**
335
347
  * Per-document memo for {@link getProjectQualifiedName} results.
336
348
  * Sibling of {@link documentQualifiedNameCache}; covers the additional
337
- * project-reference-name lookup the project-qualified variant performs.
349
+ * project-reference-name lookup the project-qualified variant performs,
350
+ * and goes stale on an in-memory rename the same way, as
351
+ * {@link NameProvider.getDocumentQualifiedName} warns.
338
352
  *
339
353
  * **Project-rename caveat.** If `Project.referenceName` mutates without
340
354
  * a corresponding document rebuild, cached entries become stale. Adopters
@@ -18,20 +18,31 @@ import { type TriviaPreserver } from './trivia-preserver.js';
18
18
  * restyle.** A document may legitimately end in blank lines, or deliberately in
19
19
  * none, and a write that changed one field is not the place to decide it should
20
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.
21
+ * consult — which is why the decision lives on the write path. Only a blank
22
+ * document, created or existing, has no ending to keep and takes
23
+ * {@link defaultEnding}.
22
24
  *
23
25
  * Line terminators are matched to the body the serializer emits, which is the
24
26
  * one thing here that cannot be preserved: keeping them verbatim leaves a
25
- * rewritten CRLF document with its only CRLFs at the very end.
27
+ * rewritten CRLF document with its only CRLFs at the very end. A body with no
28
+ * line break has none to match, so its ending stays as the file had it.
26
29
  */
27
30
  export class DocumentEndingPreserver implements TriviaPreserver<string> {
31
+ /** Default {@link defaultEnding}. */
32
+ static readonly DEFAULT_ENDING = '\n';
33
+
28
34
  readonly id = 'document-ending';
29
35
  readonly label = 'Document Ending';
30
36
 
37
+ /** The ending of a blank document, which a created one is: it has none of its own to keep. */
38
+ protected readonly defaultEnding: string = DocumentEndingPreserver.DEFAULT_ENDING;
39
+
31
40
  extract(document: LangiumDocument): string {
32
41
  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;
42
+ if (source.trim().length === 0) {
43
+ return this.defaultEnding;
44
+ }
45
+ return source.slice(source.trimEnd().length);
35
46
  }
36
47
 
37
48
  /**
@@ -40,10 +51,15 @@ export class DocumentEndingPreserver implements TriviaPreserver<string> {
40
51
  * tell a blank line the serializer emitted as part of a value from one that
41
52
  * is merely the end of the file, so it removes both and restores the ending
42
53
  * the old document had. A document whose final value just gained trailing
43
- * blank lines therefore loses them.
54
+ * blank lines therefore loses them. An override also takes over matching the
55
+ * ending's line terminators to the body's.
44
56
  */
45
57
  apply(serialized: string, trivia: string): string {
46
- return serialized.trimEnd() + trivia;
58
+ const body = serialized.trimEnd();
59
+ // The body's first line break, since a value may hold the other kind; with none
60
+ // there is no terminator to match, so the ending stays as extracted.
61
+ const terminator = /\r?\n/.exec(body)?.[0];
62
+ return body + (terminator === undefined ? trivia : trivia.replace(/\r?\n/g, terminator));
47
63
  }
48
64
  }
49
65
 
@@ -26,7 +26,9 @@ import { type RegistryItem } from '../../util/registry.js';
26
26
  export interface TriviaPreserver<T = unknown> extends RegistryItem {
27
27
  /**
28
28
  * Take this preserver's trivia off `document`, while it still holds the text
29
- * the write is about to replace.
29
+ * the write is about to replace. For a document being created, or one the
30
+ * store holds no text for, `document` is parsed from empty text, so its root
31
+ * may carry parser errors; a preserver answers its default for it.
30
32
  */
31
33
  extract(document: LangiumDocument): T;
32
34
 
@@ -257,17 +257,18 @@ export class HydraniumWorkspaceManager extends DefaultWorkspaceManager {
257
257
  * Warn when a seeded additional document's URI matches no registered
258
258
  * language.
259
259
  *
260
- * Such a document is added to the workspace and indexed, but every
261
- * per-language service is resolved per URI — so scope computation, naming,
262
- * validation and serialisation all fail to route to it, and it contributes
263
- * nothing to the scopes it was written to populate. Nothing else reports
264
- * this: the document exists, so no lookup errors, it is simply inert.
260
+ * Every per-language service is resolved per URI, so the workspace cannot
261
+ * load such a document: adding it throws, and the startup build fails
262
+ * with an error naming only the extension, not the document. This
263
+ * line names the document and the fix first.
265
264
  *
266
- * The usual cause is a `virtualUri` with no file extension:
267
- * `virtualUri(contributor)` yields `virtual:<contributor>`, and routing has
268
- * nothing to match on — there is no extension, and a virtual document is
269
- * never open in `TextDocuments`, so the declared-languageId rung cannot
270
- * serve it either. The convention is to end the URI with a registered
265
+ * The usual cause is a `fromModel` or `fromStringInLanguage` document
266
+ * under a `virtualUri` with no file extension: `virtualUri(contributor)`
267
+ * yields `virtual:<contributor>`, and routing has nothing to match on —
268
+ * there is no extension, and a virtual document is never open in
269
+ * `TextDocuments`, so the declared-languageId rung cannot serve it either.
270
+ * A `fromString` document under such a URI never gets here: its parse
271
+ * throws first. The convention is to end the URI with a registered
271
272
  * extension by passing it as the last segment,
272
273
  * `virtualUri(contributor, 'name.<ext>')`.
273
274
  */
@@ -277,9 +278,8 @@ export class HydraniumWorkspaceManager extends DefaultWorkspaceManager {
277
278
  }
278
279
  const extensions = this.languageRegistry.all.flatMap(language => language.LanguageMetaData.fileExtensions);
279
280
  this.tracer.warn(
280
- `Additional document '${document.uri.toString()}' matches no registered language, so every per-language ` +
281
- 'service (scope computation, naming, validation, serialization) will fail to route to it and it will ' +
282
- `contribute nothing to scopes. End its URI with a registered file extension (${extensions.join(', ')}); ` +
281
+ `Additional document '${document.uri.toString()}' matches no registered language, so the workspace ` +
282
+ `cannot load it and the startup build fails. End its URI with a registered file extension (${extensions.join(', ')}); ` +
283
283
  'for a virtual document that means passing it as the last segment, `virtualUri(contributor, "name.<ext>")`.'
284
284
  );
285
285
  }
@@ -45,12 +45,12 @@ export const VIRTUAL_SCHEME = 'virtual';
45
45
  * `virtual:<contributor>`, which has no extension and so matches no registered
46
46
  * language — a trap. Every per-language service is resolved per URI, and a
47
47
  * virtual document is never open in `TextDocuments`, so the declared-languageId
48
- * lookup rung cannot serve it either: the document is indexed but inert,
49
- * contributing nothing to the scopes it was written to populate, with no error
50
- * anywhere. Pass the extension as the last segment,
51
- * `virtualUri(contributor, 'name.<ext>')`. `HydraniumWorkspaceManager` warns
52
- * when a seeded additional document fails to route, which is where this
53
- * usually surfaces.
48
+ * lookup rung cannot serve it either, and the startup build fails. A
49
+ * `fromString` document throws while parsing; a `fromModel` or
50
+ * `fromStringInLanguage` document is built, and throws as it is added to the
51
+ * workspace, after `HydraniumWorkspaceManager` warns that it matches no
52
+ * language. Pass the extension as the last segment,
53
+ * `virtualUri(contributor, 'name.<ext>')`.
54
54
  *
55
55
  * # Relationship to the `$synthetic` node marker
56
56
  *
@@ -155,7 +155,9 @@ export interface LspServerConnection extends Harness {
155
155
  * Seam: the server-side `Connection`, NOT yet attached to a server. The caller
156
156
  * composes the services tree onto it (`createXxxServices({ connection })`) and
157
157
  * calls `startLanguageServer(services.shared)`, which wires the LSP handlers
158
- * and calls `connection.listen()`.
158
+ * and calls `connection.listen()`; a caller recording only applied edits
159
+ * skips `startLanguageServer` but still calls `listen()`, as
160
+ * {@link makeLspServerConnection} says.
159
161
  */
160
162
  readonly serverConnection: Connection;
161
163
  /** Seam: the raw client connection over the in-memory wire; escape hatch for unwrapped requests. */
@@ -333,6 +335,10 @@ function makeCountingStreamPair(): {
333
335
  * (it owns the `createServices` callback + `startLanguageServer` + `buildWorkspace`);
334
336
  * use this primitive directly when the CALLER must own the tree — e.g. to attach
335
337
  * additional protocol heads (data-server, GLSP) onto the same `shared` services.
338
+ *
339
+ * To record only {@link LspServerConnection.appliedEdits}, skip
340
+ * `startLanguageServer`, call `serverConnection.listen()`, and open through
341
+ * `TextDocuments.notifyDidOpenTextDocument`.
336
342
  */
337
343
  export function makeLspServerConnection(): LspServerConnection {
338
344
  const pair = makeCountingStreamPair();
@@ -9,7 +9,9 @@
9
9
 
10
10
  /**
11
11
  * Driver for the structured-write transform chain — the rewrite registry plus
12
- * the serializer — run the way a session's `update` runs it.
12
+ * the serializer — answering the bare serializer output, without the trivia
13
+ * and `ModelService` overrides a session's write applies: call
14
+ * `ModelService.modelToText` with a canonical URI for that text.
13
15
  *
14
16
  * # Why a driver and not a documented recipe
15
17
  *
@@ -91,7 +93,7 @@ export interface UpdatePipelineResult<TTransfer extends TransferElement = Transf
91
93
  * surface but it makes the precise assertion unavailable.
92
94
  */
93
95
  readonly rewritten: TTransfer;
94
- /** The serialized text, which is what a session's `update` applies to the store. */
96
+ /** The serializer's text, without the trivia a session's `update` carries over. */
95
97
  readonly text: string;
96
98
  }
97
99