@hydranium/core 1.0.0-next.39 → 1.0.0-next.390

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 (805) hide show
  1. package/README.md +78 -82
  2. package/lib/browser/host-heads-in-worker.d.ts +77 -0
  3. package/lib/browser/host-heads-in-worker.d.ts.map +1 -0
  4. package/lib/browser/host-heads-in-worker.js +169 -0
  5. package/lib/browser/host-heads-in-worker.js.map +1 -0
  6. package/lib/browser/index.d.ts +12 -0
  7. package/lib/browser/index.d.ts.map +1 -0
  8. package/lib/browser/index.js +17 -0
  9. package/lib/browser/index.js.map +1 -0
  10. package/lib/browser/indexeddb-file-system-store.d.ts +82 -0
  11. package/lib/browser/indexeddb-file-system-store.d.ts.map +1 -0
  12. package/lib/browser/indexeddb-file-system-store.js +181 -0
  13. package/lib/browser/indexeddb-file-system-store.js.map +1 -0
  14. package/lib/browser/start-language-server-in-worker.d.ts +32 -0
  15. package/lib/browser/start-language-server-in-worker.d.ts.map +1 -0
  16. package/lib/browser/start-language-server-in-worker.js +40 -0
  17. package/lib/browser/start-language-server-in-worker.js.map +1 -0
  18. package/lib/documents/ast-document-manager.d.ts +254 -140
  19. package/lib/documents/ast-document-manager.d.ts.map +1 -1
  20. package/lib/documents/ast-document-manager.js +311 -128
  21. package/lib/documents/ast-document-manager.js.map +1 -1
  22. package/lib/documents/client-ids.d.ts +5 -19
  23. package/lib/documents/client-ids.d.ts.map +1 -1
  24. package/lib/documents/client-ids.js +5 -19
  25. package/lib/documents/client-ids.js.map +1 -1
  26. package/lib/documents/client-session-errors.d.ts +15 -0
  27. package/lib/documents/client-session-errors.d.ts.map +1 -0
  28. package/lib/documents/client-session-errors.js +15 -0
  29. package/lib/documents/client-session-errors.js.map +1 -0
  30. package/lib/documents/client-session-registry.d.ts +146 -0
  31. package/lib/documents/client-session-registry.d.ts.map +1 -0
  32. package/lib/documents/client-session-registry.js +207 -0
  33. package/lib/documents/client-session-registry.js.map +1 -0
  34. package/lib/documents/dirty-state-tracker.d.ts +101 -0
  35. package/lib/documents/dirty-state-tracker.d.ts.map +1 -0
  36. package/lib/documents/dirty-state-tracker.js +85 -0
  37. package/lib/documents/dirty-state-tracker.js.map +1 -0
  38. package/lib/documents/document-release-handler.d.ts +204 -0
  39. package/lib/documents/document-release-handler.d.ts.map +1 -0
  40. package/lib/documents/document-release-handler.js +317 -0
  41. package/lib/documents/document-release-handler.js.map +1 -0
  42. package/lib/documents/document-release-scheduler.d.ts +67 -0
  43. package/lib/documents/document-release-scheduler.d.ts.map +1 -0
  44. package/lib/documents/document-release-scheduler.js +80 -0
  45. package/lib/documents/document-release-scheduler.js.map +1 -0
  46. package/lib/documents/file-system-task-queue.d.ts +52 -0
  47. package/lib/documents/file-system-task-queue.d.ts.map +1 -0
  48. package/lib/documents/file-system-task-queue.js +40 -0
  49. package/lib/documents/file-system-task-queue.js.map +1 -0
  50. package/lib/documents/hydranium-text-documents.d.ts +453 -282
  51. package/lib/documents/hydranium-text-documents.d.ts.map +1 -1
  52. package/lib/documents/hydranium-text-documents.js +918 -504
  53. package/lib/documents/hydranium-text-documents.js.map +1 -1
  54. package/lib/documents/index.d.ts +10 -2
  55. package/lib/documents/index.d.ts.map +1 -1
  56. package/lib/documents/index.js +10 -2
  57. package/lib/documents/index.js.map +1 -1
  58. package/lib/documents/language-client-shadow.d.ts +234 -0
  59. package/lib/documents/language-client-shadow.d.ts.map +1 -0
  60. package/lib/documents/language-client-shadow.js +306 -0
  61. package/lib/documents/language-client-shadow.js.map +1 -0
  62. package/lib/documents/model-ledger.d.ts +47 -0
  63. package/lib/documents/model-ledger.d.ts.map +1 -0
  64. package/lib/documents/model-ledger.js +30 -0
  65. package/lib/documents/model-ledger.js.map +1 -0
  66. package/lib/documents/text-ledger.d.ts +64 -0
  67. package/lib/documents/text-ledger.d.ts.map +1 -0
  68. package/lib/documents/text-ledger.js +63 -0
  69. package/lib/documents/text-ledger.js.map +1 -0
  70. package/lib/documents/version-sync-service.d.ts +153 -0
  71. package/lib/documents/version-sync-service.d.ts.map +1 -0
  72. package/lib/documents/version-sync-service.js +186 -0
  73. package/lib/documents/version-sync-service.js.map +1 -0
  74. package/lib/index.d.ts +3 -0
  75. package/lib/index.d.ts.map +1 -1
  76. package/lib/index.js +3 -0
  77. package/lib/index.js.map +1 -1
  78. package/lib/langium/ast-extension/ast-extension-service.d.ts +42 -31
  79. package/lib/langium/ast-extension/ast-extension-service.d.ts.map +1 -1
  80. package/lib/langium/ast-extension/ast-extension-service.js +45 -19
  81. package/lib/langium/ast-extension/ast-extension-service.js.map +1 -1
  82. package/lib/langium/ast-extension/ast-node-builder.d.ts +12 -7
  83. package/lib/langium/ast-extension/ast-node-builder.d.ts.map +1 -1
  84. package/lib/langium/ast-extension/ast-node-builder.js +25 -9
  85. package/lib/langium/ast-extension/ast-node-builder.js.map +1 -1
  86. package/lib/langium/bootstrap.d.ts +49 -36
  87. package/lib/langium/bootstrap.d.ts.map +1 -1
  88. package/lib/langium/bootstrap.js +75 -36
  89. package/lib/langium/bootstrap.js.map +1 -1
  90. package/lib/langium/build-phase-pass/build-phase-pass-service.d.ts +9 -9
  91. package/lib/langium/build-phase-pass/build-phase-pass-service.d.ts.map +1 -1
  92. package/lib/langium/build-phase-pass/build-phase-pass-service.js +25 -16
  93. package/lib/langium/build-phase-pass/build-phase-pass-service.js.map +1 -1
  94. package/lib/langium/build-phase-pass/build-phase-pass.d.ts +20 -8
  95. package/lib/langium/build-phase-pass/build-phase-pass.d.ts.map +1 -1
  96. package/lib/langium/build-phase-pass/build-phase-pass.js +20 -0
  97. package/lib/langium/build-phase-pass/build-phase-pass.js.map +1 -1
  98. package/lib/langium/composite-ast-reflection.d.ts +46 -0
  99. package/lib/langium/composite-ast-reflection.d.ts.map +1 -0
  100. package/lib/langium/composite-ast-reflection.js +97 -0
  101. package/lib/langium/composite-ast-reflection.js.map +1 -0
  102. package/lib/langium/config/configuration-provider.d.ts +81 -0
  103. package/lib/langium/config/configuration-provider.d.ts.map +1 -0
  104. package/lib/langium/config/configuration-provider.js +112 -0
  105. package/lib/langium/config/configuration-provider.js.map +1 -0
  106. package/lib/langium/config/index.d.ts +1 -0
  107. package/lib/langium/config/index.d.ts.map +1 -1
  108. package/lib/langium/config/index.js +1 -0
  109. package/lib/langium/config/index.js.map +1 -1
  110. package/lib/langium/config/settings.js.map +1 -1
  111. package/lib/langium/diagnostics/console-tee.d.ts +27 -0
  112. package/lib/langium/diagnostics/console-tee.d.ts.map +1 -0
  113. package/lib/langium/diagnostics/console-tee.js +141 -0
  114. package/lib/langium/diagnostics/console-tee.js.map +1 -0
  115. package/lib/langium/diagnostics/hydranium-langium-profiler.d.ts +9 -20
  116. package/lib/langium/diagnostics/hydranium-langium-profiler.d.ts.map +1 -1
  117. package/lib/langium/diagnostics/hydranium-langium-profiler.js +15 -24
  118. package/lib/langium/diagnostics/hydranium-langium-profiler.js.map +1 -1
  119. package/lib/langium/diagnostics/log-preamble.js +1 -1
  120. package/lib/langium/diagnostics/log-preamble.js.map +1 -1
  121. package/lib/langium/diagnostics/logger.d.ts +25 -6
  122. package/lib/langium/diagnostics/logger.d.ts.map +1 -1
  123. package/lib/langium/diagnostics/logger.js +49 -17
  124. package/lib/langium/diagnostics/logger.js.map +1 -1
  125. package/lib/langium/diagnostics/lsp-logger.d.ts +25 -0
  126. package/lib/langium/diagnostics/lsp-logger.d.ts.map +1 -1
  127. package/lib/langium/diagnostics/lsp-logger.js +81 -46
  128. package/lib/langium/diagnostics/lsp-logger.js.map +1 -1
  129. package/lib/langium/diagnostics/server-tracer.d.ts.map +1 -1
  130. package/lib/langium/diagnostics/server-tracer.js.map +1 -1
  131. package/lib/langium/document-builder/build-pipeline-integration.d.ts +21 -1
  132. package/lib/langium/document-builder/build-pipeline-integration.d.ts.map +1 -1
  133. package/lib/langium/document-builder/build-pipeline-integration.js +43 -30
  134. package/lib/langium/document-builder/build-pipeline-integration.js.map +1 -1
  135. package/lib/langium/document-builder/build-session.d.ts +6 -5
  136. package/lib/langium/document-builder/build-session.d.ts.map +1 -1
  137. package/lib/langium/document-builder/build-session.js +5 -5
  138. package/lib/langium/document-builder/build-session.js.map +1 -1
  139. package/lib/langium/document-builder/document-builder.d.ts +390 -40
  140. package/lib/langium/document-builder/document-builder.d.ts.map +1 -1
  141. package/lib/langium/document-builder/document-builder.js +635 -102
  142. package/lib/langium/document-builder/document-builder.js.map +1 -1
  143. package/lib/langium/documentation/comment-provider.d.ts.map +1 -1
  144. package/lib/langium/documentation/comment-provider.js.map +1 -1
  145. package/lib/langium/integration-services.d.ts +20 -23
  146. package/lib/langium/integration-services.d.ts.map +1 -1
  147. package/lib/langium/integration-services.js.map +1 -1
  148. package/lib/langium/integrity/integrity-rule.d.ts +22 -8
  149. package/lib/langium/integrity/integrity-rule.d.ts.map +1 -1
  150. package/lib/langium/integrity/integrity-rule.js.map +1 -1
  151. package/lib/langium/integrity/integrity-service.d.ts +217 -29
  152. package/lib/langium/integrity/integrity-service.d.ts.map +1 -1
  153. package/lib/langium/integrity/integrity-service.js +588 -84
  154. package/lib/langium/integrity/integrity-service.js.map +1 -1
  155. package/lib/langium/keys/containment.d.ts +3 -2
  156. package/lib/langium/keys/containment.d.ts.map +1 -1
  157. package/lib/langium/keys/containment.js.map +1 -1
  158. package/lib/langium/keys/name-based-key-provider.d.ts +17 -10
  159. package/lib/langium/keys/name-based-key-provider.d.ts.map +1 -1
  160. package/lib/langium/keys/name-based-key-provider.js +24 -12
  161. package/lib/langium/keys/name-based-key-provider.js.map +1 -1
  162. package/lib/langium/keys/positional-key-provider.d.ts.map +1 -1
  163. package/lib/langium/keys/positional-key-provider.js.map +1 -1
  164. package/lib/langium/labeling/label-provider.d.ts.map +1 -1
  165. package/lib/langium/labeling/label-provider.js.map +1 -1
  166. package/lib/langium/language-module.d.ts +30 -5
  167. package/lib/langium/language-module.d.ts.map +1 -1
  168. package/lib/langium/language-module.js +21 -5
  169. package/lib/langium/language-module.js.map +1 -1
  170. package/lib/langium/model-service/client-session.d.ts +405 -0
  171. package/lib/langium/model-service/client-session.d.ts.map +1 -0
  172. package/lib/langium/model-service/client-session.js +479 -0
  173. package/lib/langium/model-service/client-session.js.map +1 -0
  174. package/lib/langium/model-service/index.d.ts +2 -0
  175. package/lib/langium/model-service/index.d.ts.map +1 -1
  176. package/lib/langium/model-service/index.js +2 -0
  177. package/lib/langium/model-service/index.js.map +1 -1
  178. package/lib/langium/model-service/model-events.d.ts +68 -0
  179. package/lib/langium/model-service/model-events.d.ts.map +1 -0
  180. package/lib/langium/model-service/model-events.js +10 -0
  181. package/lib/langium/model-service/model-events.js.map +1 -0
  182. package/lib/langium/model-service/model-service.d.ts +441 -323
  183. package/lib/langium/model-service/model-service.d.ts.map +1 -1
  184. package/lib/langium/model-service/model-service.js +602 -407
  185. package/lib/langium/model-service/model-service.js.map +1 -1
  186. package/lib/langium/module.d.ts +96 -37
  187. package/lib/langium/module.d.ts.map +1 -1
  188. package/lib/langium/module.js +41 -29
  189. package/lib/langium/module.js.map +1 -1
  190. package/lib/langium/naming/name-provider.d.ts +32 -7
  191. package/lib/langium/naming/name-provider.d.ts.map +1 -1
  192. package/lib/langium/naming/name-provider.js +10 -4
  193. package/lib/langium/naming/name-provider.js.map +1 -1
  194. package/lib/langium/naming/name-separator-validation.d.ts.map +1 -1
  195. package/lib/langium/project/abstract-project-manager.d.ts +184 -13
  196. package/lib/langium/project/abstract-project-manager.d.ts.map +1 -1
  197. package/lib/langium/project/abstract-project-manager.js +336 -16
  198. package/lib/langium/project/abstract-project-manager.js.map +1 -1
  199. package/lib/langium/project/project-change-event.d.ts +22 -3
  200. package/lib/langium/project/project-change-event.d.ts.map +1 -1
  201. package/lib/langium/project/project-manager.d.ts +28 -2
  202. package/lib/langium/project/project-manager.d.ts.map +1 -1
  203. package/lib/langium/project/single-project-manager.d.ts +7 -1
  204. package/lib/langium/project/single-project-manager.d.ts.map +1 -1
  205. package/lib/langium/project/single-project-manager.js +12 -0
  206. package/lib/langium/project/single-project-manager.js.map +1 -1
  207. package/lib/langium/residency/cst-residency-service.d.ts +92 -52
  208. package/lib/langium/residency/cst-residency-service.d.ts.map +1 -1
  209. package/lib/langium/residency/cst-residency-service.js +154 -58
  210. package/lib/langium/residency/cst-residency-service.js.map +1 -1
  211. package/lib/langium/scope/ast-node-description-provider.d.ts +46 -10
  212. package/lib/langium/scope/ast-node-description-provider.d.ts.map +1 -1
  213. package/lib/langium/scope/ast-node-description-provider.js +88 -15
  214. package/lib/langium/scope/ast-node-description-provider.js.map +1 -1
  215. package/lib/langium/scope/built-local-symbols.d.ts +12 -0
  216. package/lib/langium/scope/built-local-symbols.d.ts.map +1 -0
  217. package/lib/langium/scope/built-local-symbols.js +11 -0
  218. package/lib/langium/scope/built-local-symbols.js.map +1 -0
  219. package/lib/langium/scope/fallback-scope.d.ts +27 -0
  220. package/lib/langium/scope/fallback-scope.d.ts.map +1 -0
  221. package/lib/langium/scope/fallback-scope.js +35 -0
  222. package/lib/langium/scope/fallback-scope.js.map +1 -0
  223. package/lib/langium/scope/hydranium-scope-computation.d.ts +21 -18
  224. package/lib/langium/scope/hydranium-scope-computation.d.ts.map +1 -1
  225. package/lib/langium/scope/hydranium-scope-computation.js +38 -39
  226. package/lib/langium/scope/hydranium-scope-computation.js.map +1 -1
  227. package/lib/langium/scope/hydranium-scope-provider.d.ts +122 -32
  228. package/lib/langium/scope/hydranium-scope-provider.d.ts.map +1 -1
  229. package/lib/langium/scope/hydranium-scope-provider.js +217 -39
  230. package/lib/langium/scope/hydranium-scope-provider.js.map +1 -1
  231. package/lib/langium/scope/index.d.ts +1 -0
  232. package/lib/langium/scope/index.d.ts.map +1 -1
  233. package/lib/langium/scope/index.js +1 -0
  234. package/lib/langium/scope/index.js.map +1 -1
  235. package/lib/langium/scope/reference-builder.d.ts.map +1 -1
  236. package/lib/langium/scope/reference-builder.js.map +1 -1
  237. package/lib/langium/scope/reference-candidate-provider.d.ts +6 -5
  238. package/lib/langium/scope/reference-candidate-provider.d.ts.map +1 -1
  239. package/lib/langium/scope/reference-candidate-provider.js +3 -2
  240. package/lib/langium/scope/reference-candidate-provider.js.map +1 -1
  241. package/lib/langium/scope/scope-extension-service.d.ts +69 -37
  242. package/lib/langium/scope/scope-extension-service.d.ts.map +1 -1
  243. package/lib/langium/scope/scope-extension-service.js +102 -29
  244. package/lib/langium/scope/scope-extension-service.js.map +1 -1
  245. package/lib/langium/scope/scoped-ast-node-description.d.ts +1 -3
  246. package/lib/langium/scope/scoped-ast-node-description.d.ts.map +1 -1
  247. package/lib/langium/scope/scoped-ast-node-description.js +1 -3
  248. package/lib/langium/scope/scoped-ast-node-description.js.map +1 -1
  249. package/lib/langium/serialization/abstract-serializer.d.ts +38 -27
  250. package/lib/langium/serialization/abstract-serializer.d.ts.map +1 -1
  251. package/lib/langium/serialization/abstract-serializer.js +54 -30
  252. package/lib/langium/serialization/abstract-serializer.js.map +1 -1
  253. package/lib/langium/serialization/json-serializer.d.ts +8 -1
  254. package/lib/langium/serialization/json-serializer.d.ts.map +1 -1
  255. package/lib/langium/serialization/json-serializer.js +13 -3
  256. package/lib/langium/serialization/json-serializer.js.map +1 -1
  257. package/lib/langium/serialization/serializer.d.ts +1 -1
  258. package/lib/langium/serialization/yaml-serializer.d.ts +12 -0
  259. package/lib/langium/serialization/yaml-serializer.d.ts.map +1 -1
  260. package/lib/langium/serialization/yaml-serializer.js +24 -4
  261. package/lib/langium/serialization/yaml-serializer.js.map +1 -1
  262. package/lib/langium/service-registry.d.ts +29 -1
  263. package/lib/langium/service-registry.d.ts.map +1 -1
  264. package/lib/langium/service-registry.js +37 -0
  265. package/lib/langium/service-registry.js.map +1 -1
  266. package/lib/langium/shared-services.d.ts +10 -4
  267. package/lib/langium/shared-services.d.ts.map +1 -1
  268. package/lib/langium/shared-services.js.map +1 -1
  269. package/lib/langium/single-langium-copy.d.ts +22 -0
  270. package/lib/langium/single-langium-copy.d.ts.map +1 -0
  271. package/lib/langium/single-langium-copy.js +36 -0
  272. package/lib/langium/single-langium-copy.js.map +1 -0
  273. package/lib/langium/transfer/transfer-encoder.d.ts +60 -39
  274. package/lib/langium/transfer/transfer-encoder.d.ts.map +1 -1
  275. package/lib/langium/transfer/transfer-encoder.js +35 -24
  276. package/lib/langium/transfer/transfer-encoder.js.map +1 -1
  277. package/lib/langium/trivia/comment-preserver.d.ts +543 -0
  278. package/lib/langium/trivia/comment-preserver.d.ts.map +1 -0
  279. package/lib/langium/trivia/comment-preserver.js +1165 -0
  280. package/lib/langium/trivia/comment-preserver.js.map +1 -0
  281. package/lib/langium/trivia/document-ending-preserver.d.ts +51 -0
  282. package/lib/langium/trivia/document-ending-preserver.d.ts.map +1 -0
  283. package/lib/langium/trivia/document-ending-preserver.js +62 -0
  284. package/lib/langium/trivia/document-ending-preserver.js.map +1 -0
  285. package/lib/langium/trivia/index.d.ts +14 -0
  286. package/lib/langium/trivia/index.d.ts.map +1 -0
  287. package/lib/langium/trivia/index.js +14 -0
  288. package/lib/langium/trivia/index.js.map +1 -0
  289. package/lib/langium/trivia/trivia-contribution.d.ts +37 -0
  290. package/lib/langium/trivia/trivia-contribution.d.ts.map +1 -0
  291. package/lib/langium/trivia/trivia-contribution.js +10 -0
  292. package/lib/langium/trivia/trivia-contribution.js.map +1 -0
  293. package/lib/langium/trivia/trivia-preserver.d.ts +52 -0
  294. package/lib/langium/trivia/trivia-preserver.d.ts.map +1 -0
  295. package/lib/langium/trivia/trivia-preserver.js +10 -0
  296. package/lib/langium/trivia/trivia-preserver.js.map +1 -0
  297. package/lib/langium/trivia/trivia-service.d.ts +70 -0
  298. package/lib/langium/trivia/trivia-service.d.ts.map +1 -0
  299. package/lib/langium/trivia/trivia-service.js +56 -0
  300. package/lib/langium/trivia/trivia-service.js.map +1 -0
  301. package/lib/langium/update-rewrite/normalize-empty-strings.d.ts.map +1 -1
  302. package/lib/langium/update-rewrite/update-rewrite-service.d.ts +1 -1
  303. package/lib/langium/update-rewrite/update-rewrite-service.d.ts.map +1 -1
  304. package/lib/langium/update-rewrite/update-rewrite-service.js.map +1 -1
  305. package/lib/langium/update-rewrite/update-rewrite.d.ts +1 -1
  306. package/lib/langium/validation/document-validator.d.ts +168 -23
  307. package/lib/langium/validation/document-validator.d.ts.map +1 -1
  308. package/lib/langium/validation/document-validator.js +264 -26
  309. package/lib/langium/validation/document-validator.js.map +1 -1
  310. package/lib/langium/validation/validation-contribution-collector.d.ts +9 -1
  311. package/lib/langium/validation/validation-contribution-collector.d.ts.map +1 -1
  312. package/lib/langium/validation/validation-contribution-collector.js +1 -1
  313. package/lib/langium/validation/validation-contribution-collector.js.map +1 -1
  314. package/lib/langium/workspace/ast-node-locator.d.ts +24 -0
  315. package/lib/langium/workspace/ast-node-locator.d.ts.map +1 -0
  316. package/lib/langium/workspace/ast-node-locator.js +32 -0
  317. package/lib/langium/workspace/ast-node-locator.js.map +1 -0
  318. package/lib/langium/workspace/document-uri-policy.d.ts +24 -15
  319. package/lib/langium/workspace/document-uri-policy.d.ts.map +1 -1
  320. package/lib/langium/workspace/document-uri-policy.js +22 -13
  321. package/lib/langium/workspace/document-uri-policy.js.map +1 -1
  322. package/lib/langium/workspace/file-not-found.d.ts +23 -0
  323. package/lib/langium/workspace/file-not-found.d.ts.map +1 -0
  324. package/lib/langium/workspace/file-not-found.js +31 -0
  325. package/lib/langium/workspace/file-not-found.js.map +1 -0
  326. package/lib/langium/workspace/file-system-provider.d.ts +167 -8
  327. package/lib/langium/workspace/file-system-provider.d.ts.map +1 -1
  328. package/lib/langium/workspace/file-system-provider.js +115 -13
  329. package/lib/langium/workspace/file-system-provider.js.map +1 -1
  330. package/lib/langium/workspace/hydranium-langium-document-factory.d.ts +78 -4
  331. package/lib/langium/workspace/hydranium-langium-document-factory.d.ts.map +1 -1
  332. package/lib/langium/workspace/hydranium-langium-document-factory.js +188 -5
  333. package/lib/langium/workspace/hydranium-langium-document-factory.js.map +1 -1
  334. package/lib/langium/workspace/hydranium-workspace-lock.d.ts +50 -1
  335. package/lib/langium/workspace/hydranium-workspace-lock.d.ts.map +1 -1
  336. package/lib/langium/workspace/hydranium-workspace-lock.js +73 -1
  337. package/lib/langium/workspace/hydranium-workspace-lock.js.map +1 -1
  338. package/lib/langium/workspace/hydranium-workspace-manager.d.ts +105 -21
  339. package/lib/langium/workspace/hydranium-workspace-manager.d.ts.map +1 -1
  340. package/lib/langium/workspace/hydranium-workspace-manager.js +186 -31
  341. package/lib/langium/workspace/hydranium-workspace-manager.js.map +1 -1
  342. package/lib/langium/workspace/in-memory-file-system-provider.d.ts +26 -9
  343. package/lib/langium/workspace/in-memory-file-system-provider.d.ts.map +1 -1
  344. package/lib/langium/workspace/in-memory-file-system-provider.js +45 -22
  345. package/lib/langium/workspace/in-memory-file-system-provider.js.map +1 -1
  346. package/lib/langium/workspace/index-manager.d.ts.map +1 -1
  347. package/lib/langium/workspace/index-manager.js.map +1 -1
  348. package/lib/langium/workspace/index.d.ts +3 -0
  349. package/lib/langium/workspace/index.d.ts.map +1 -1
  350. package/lib/langium/workspace/index.js +3 -0
  351. package/lib/langium/workspace/index.js.map +1 -1
  352. package/lib/langium/workspace/initialize-workspace.d.ts +40 -3
  353. package/lib/langium/workspace/initialize-workspace.d.ts.map +1 -1
  354. package/lib/langium/workspace/initialize-workspace.js +68 -3
  355. package/lib/langium/workspace/initialize-workspace.js.map +1 -1
  356. package/lib/langium/workspace/langium-documents.d.ts +7 -0
  357. package/lib/langium/workspace/langium-documents.d.ts.map +1 -1
  358. package/lib/langium/workspace/langium-documents.js +18 -1
  359. package/lib/langium/workspace/langium-documents.js.map +1 -1
  360. package/lib/langium/workspace/persistent-file-system-provider.d.ts +16 -10
  361. package/lib/langium/workspace/persistent-file-system-provider.d.ts.map +1 -1
  362. package/lib/langium/workspace/persistent-file-system-provider.js +25 -13
  363. package/lib/langium/workspace/persistent-file-system-provider.js.map +1 -1
  364. package/lib/{documents → langium/workspace}/self-save-registry.d.ts +7 -9
  365. package/lib/langium/workspace/self-save-registry.d.ts.map +1 -0
  366. package/lib/{documents → langium/workspace}/self-save-registry.js +9 -11
  367. package/lib/langium/workspace/self-save-registry.js.map +1 -0
  368. package/lib/langium/workspace/syntax-errors.d.ts +23 -0
  369. package/lib/langium/workspace/syntax-errors.d.ts.map +1 -0
  370. package/lib/langium/workspace/syntax-errors.js +29 -0
  371. package/lib/langium/workspace/syntax-errors.js.map +1 -0
  372. package/lib/langium/workspace/virtual-document.d.ts +55 -21
  373. package/lib/langium/workspace/virtual-document.d.ts.map +1 -1
  374. package/lib/langium/workspace/virtual-document.js +87 -24
  375. package/lib/langium/workspace/virtual-document.js.map +1 -1
  376. package/lib/langium/workspace/write-lock-scope.d.ts +22 -10
  377. package/lib/langium/workspace/write-lock-scope.d.ts.map +1 -1
  378. package/lib/langium/workspace/write-lock-scope.js +15 -5
  379. package/lib/langium/workspace/write-lock-scope.js.map +1 -1
  380. package/lib/launcher/integrated-server.d.ts +3 -1
  381. package/lib/launcher/integrated-server.d.ts.map +1 -1
  382. package/lib/locale/server-locale.d.ts +6 -6
  383. package/lib/locale/server-locale.d.ts.map +1 -1
  384. package/lib/locale/server-locale.js.map +1 -1
  385. package/lib/lsp/completion/hydranium-completion-provider.d.ts +44 -2
  386. package/lib/lsp/completion/hydranium-completion-provider.d.ts.map +1 -1
  387. package/lib/lsp/completion/hydranium-completion-provider.js +68 -0
  388. package/lib/lsp/completion/hydranium-completion-provider.js.map +1 -1
  389. package/lib/lsp/connection-features.d.ts +23 -0
  390. package/lib/lsp/connection-features.d.ts.map +1 -0
  391. package/lib/lsp/connection-features.js +60 -0
  392. package/lib/lsp/connection-features.js.map +1 -0
  393. package/lib/lsp/diagnostics-connection.d.ts +27 -0
  394. package/lib/lsp/diagnostics-connection.d.ts.map +1 -0
  395. package/lib/lsp/diagnostics-connection.js +53 -0
  396. package/lib/lsp/diagnostics-connection.js.map +1 -0
  397. package/lib/lsp/edit-requests-guard.d.ts +31 -0
  398. package/lib/lsp/edit-requests-guard.d.ts.map +1 -0
  399. package/lib/lsp/edit-requests-guard.js +125 -0
  400. package/lib/lsp/edit-requests-guard.js.map +1 -0
  401. package/lib/lsp/hydranium-document-update-handler.d.ts +126 -82
  402. package/lib/lsp/hydranium-document-update-handler.d.ts.map +1 -1
  403. package/lib/lsp/hydranium-document-update-handler.js +234 -101
  404. package/lib/lsp/hydranium-document-update-handler.js.map +1 -1
  405. package/lib/lsp/index.d.ts +6 -2
  406. package/lib/lsp/index.d.ts.map +1 -1
  407. package/lib/lsp/index.js +6 -2
  408. package/lib/lsp/index.js.map +1 -1
  409. package/lib/lsp/lsp-latency.d.ts +39 -0
  410. package/lib/lsp/lsp-latency.d.ts.map +1 -0
  411. package/lib/lsp/lsp-latency.js +58 -0
  412. package/lib/lsp/lsp-latency.js.map +1 -0
  413. package/lib/lsp/save-text-document.d.ts +31 -0
  414. package/lib/lsp/save-text-document.d.ts.map +1 -0
  415. package/lib/lsp/save-text-document.js +52 -0
  416. package/lib/lsp/save-text-document.js.map +1 -0
  417. package/lib/lsp/semantic-token-provider.d.ts +58 -2
  418. package/lib/lsp/semantic-token-provider.d.ts.map +1 -1
  419. package/lib/lsp/semantic-token-provider.js +98 -9
  420. package/lib/lsp/semantic-token-provider.js.map +1 -1
  421. package/lib/lsp/shared-module.d.ts +4 -8
  422. package/lib/lsp/shared-module.d.ts.map +1 -1
  423. package/lib/lsp/shared-module.js.map +1 -1
  424. package/lib/lsp/start-language-server.d.ts +12 -7
  425. package/lib/lsp/start-language-server.d.ts.map +1 -1
  426. package/lib/lsp/start-language-server.js +21 -7
  427. package/lib/lsp/start-language-server.js.map +1 -1
  428. package/lib/messages/carriers.d.ts +4 -6
  429. package/lib/messages/carriers.d.ts.map +1 -1
  430. package/lib/messages/carriers.js +4 -6
  431. package/lib/messages/carriers.js.map +1 -1
  432. package/lib/messages/index.d.ts +2 -2
  433. package/lib/messages/index.d.ts.map +1 -1
  434. package/lib/messages/index.js +2 -2
  435. package/lib/messages/index.js.map +1 -1
  436. package/lib/messages/renderer.d.ts +10 -4
  437. package/lib/messages/renderer.d.ts.map +1 -1
  438. package/lib/messages/renderer.js +6 -0
  439. package/lib/messages/renderer.js.map +1 -1
  440. package/lib/node/event-loop-monitor.d.ts.map +1 -1
  441. package/lib/node/event-loop-monitor.js +3 -2
  442. package/lib/node/event-loop-monitor.js.map +1 -1
  443. package/lib/node/heap-ceiling.d.ts +61 -0
  444. package/lib/node/heap-ceiling.d.ts.map +1 -0
  445. package/lib/node/heap-ceiling.js +72 -0
  446. package/lib/node/heap-ceiling.js.map +1 -0
  447. package/lib/node/index.d.ts +1 -1
  448. package/lib/node/index.d.ts.map +1 -1
  449. package/lib/node/index.js +5 -1
  450. package/lib/node/index.js.map +1 -1
  451. package/lib/node/latency-from-env.d.ts +1 -1
  452. package/lib/node/latency-from-env.js +1 -1
  453. package/lib/node/lint-grammar.js.map +1 -1
  454. package/lib/node/log-file-sink.d.ts +3 -2
  455. package/lib/node/log-file-sink.d.ts.map +1 -1
  456. package/lib/node/log-file-sink.js +6 -2
  457. package/lib/node/log-file-sink.js.map +1 -1
  458. package/lib/node/log-preamble-node.d.ts +5 -3
  459. package/lib/node/log-preamble-node.d.ts.map +1 -1
  460. package/lib/node/log-preamble-node.js +13 -10
  461. package/lib/node/log-preamble-node.js.map +1 -1
  462. package/lib/node/measure-memory.d.ts.map +1 -1
  463. package/lib/node/measure-memory.js +5 -4
  464. package/lib/node/measure-memory.js.map +1 -1
  465. package/lib/node/memory-monitor.d.ts +2 -2
  466. package/lib/node/memory-monitor.d.ts.map +1 -1
  467. package/lib/node/memory-monitor.js +9 -10
  468. package/lib/node/memory-monitor.js.map +1 -1
  469. package/lib/node/node-file-system-provider.d.ts +67 -9
  470. package/lib/node/node-file-system-provider.d.ts.map +1 -1
  471. package/lib/node/node-file-system-provider.js +198 -53
  472. package/lib/node/node-file-system-provider.js.map +1 -1
  473. package/lib/node/profile-capture.d.ts +12 -4
  474. package/lib/node/profile-capture.d.ts.map +1 -1
  475. package/lib/node/profile-capture.js +8 -6
  476. package/lib/node/profile-capture.js.map +1 -1
  477. package/lib/node/profile-digest.js.map +1 -1
  478. package/lib/node/profiling-run.d.ts +13 -3
  479. package/lib/node/profiling-run.d.ts.map +1 -1
  480. package/lib/node/profiling-run.js +17 -8
  481. package/lib/node/profiling-run.js.map +1 -1
  482. package/lib/node/rename-over-open-readers.d.ts +61 -0
  483. package/lib/node/rename-over-open-readers.d.ts.map +1 -0
  484. package/lib/node/rename-over-open-readers.js +80 -0
  485. package/lib/node/rename-over-open-readers.js.map +1 -0
  486. package/lib/node/server-diagnostics.js +1 -1
  487. package/lib/node/server-diagnostics.js.map +1 -1
  488. package/lib/node/server-state-snapshot.d.ts +6 -5
  489. package/lib/node/server-state-snapshot.d.ts.map +1 -1
  490. package/lib/node/server-state-snapshot.js +20 -17
  491. package/lib/node/server-state-snapshot.js.map +1 -1
  492. package/lib/node/socket-launcher.d.ts +43 -7
  493. package/lib/node/socket-launcher.d.ts.map +1 -1
  494. package/lib/node/socket-launcher.js +42 -7
  495. package/lib/node/socket-launcher.js.map +1 -1
  496. package/lib/node/startup-cpu-profile.d.ts +29 -0
  497. package/lib/node/startup-cpu-profile.d.ts.map +1 -0
  498. package/lib/node/startup-cpu-profile.js +91 -0
  499. package/lib/node/startup-cpu-profile.js.map +1 -0
  500. package/lib/node/stdio-launcher.d.ts +13 -13
  501. package/lib/node/stdio-launcher.d.ts.map +1 -1
  502. package/lib/node/stdio-launcher.js +21 -11
  503. package/lib/node/stdio-launcher.js.map +1 -1
  504. package/lib/node/validate-workspace.d.ts +1 -1
  505. package/lib/node/validate-workspace.d.ts.map +1 -1
  506. package/lib/node/validate-workspace.js +5 -1
  507. package/lib/node/validate-workspace.js.map +1 -1
  508. package/lib/testing/document-uri-policy-conformance.js +1 -1
  509. package/lib/testing/document-uri-policy-conformance.js.map +1 -1
  510. package/lib/testing/fake-description.js.map +1 -1
  511. package/lib/testing/fake-document.d.ts +2 -1
  512. package/lib/testing/fake-document.d.ts.map +1 -1
  513. package/lib/testing/fake-document.js.map +1 -1
  514. package/lib/testing/index.d.ts +1 -1
  515. package/lib/testing/index.d.ts.map +1 -1
  516. package/lib/testing/index.js +2 -1
  517. package/lib/testing/index.js.map +1 -1
  518. package/lib/testing/langium-test-helpers.d.ts.map +1 -1
  519. package/lib/testing/langium-test-helpers.js +1 -0
  520. package/lib/testing/langium-test-helpers.js.map +1 -1
  521. package/lib/testing/make-noop-language-services.js.map +1 -1
  522. package/lib/testing/make-noop-shared-services.js.map +1 -1
  523. package/lib/testing/make-test-services.d.ts +45 -14
  524. package/lib/testing/make-test-services.d.ts.map +1 -1
  525. package/lib/testing/make-test-services.js +23 -7
  526. package/lib/testing/make-test-services.js.map +1 -1
  527. package/lib/testing/make-test-tracer.d.ts +12 -0
  528. package/lib/testing/make-test-tracer.d.ts.map +1 -1
  529. package/lib/testing/make-test-tracer.js +13 -0
  530. package/lib/testing/make-test-tracer.js.map +1 -1
  531. package/lib/testing/node/applied-edit-capture.d.ts +29 -0
  532. package/lib/testing/node/applied-edit-capture.d.ts.map +1 -0
  533. package/lib/testing/node/applied-edit-capture.js +117 -0
  534. package/lib/testing/node/applied-edit-capture.js.map +1 -0
  535. package/lib/testing/node/golden-corpus.js.map +1 -1
  536. package/lib/testing/node/index.d.ts +4 -1
  537. package/lib/testing/node/index.d.ts.map +1 -1
  538. package/lib/testing/node/index.js +4 -1
  539. package/lib/testing/node/index.js.map +1 -1
  540. package/lib/testing/node/lsp-harness.d.ts.map +1 -1
  541. package/lib/testing/node/lsp-harness.js +2 -0
  542. package/lib/testing/node/lsp-harness.js.map +1 -1
  543. package/lib/testing/node/lsp-server-connection.d.ts +21 -6
  544. package/lib/testing/node/lsp-server-connection.d.ts.map +1 -1
  545. package/lib/testing/node/lsp-server-connection.js +49 -106
  546. package/lib/testing/node/lsp-server-connection.js.map +1 -1
  547. package/lib/testing/node/scratch-workspace.d.ts +2 -1
  548. package/lib/testing/node/scratch-workspace.d.ts.map +1 -1
  549. package/lib/testing/node/scratch-workspace.js +12 -6
  550. package/lib/testing/node/scratch-workspace.js.map +1 -1
  551. package/lib/testing/node/settle-server.d.ts +33 -0
  552. package/lib/testing/node/settle-server.d.ts.map +1 -0
  553. package/lib/testing/node/settle-server.js +84 -0
  554. package/lib/testing/node/settle-server.js.map +1 -0
  555. package/lib/testing/node/spawned-server.d.ts +30 -1
  556. package/lib/testing/node/spawned-server.d.ts.map +1 -1
  557. package/lib/testing/node/spawned-server.js +39 -2
  558. package/lib/testing/node/spawned-server.js.map +1 -1
  559. package/lib/testing/playwright/browser-capture-bridge.js.map +1 -1
  560. package/lib/testing/playwright/e2e-profiling.js.map +1 -1
  561. package/lib/testing/playwright/flaky-network-proxy.d.ts +76 -0
  562. package/lib/testing/playwright/flaky-network-proxy.d.ts.map +1 -0
  563. package/lib/testing/playwright/flaky-network-proxy.js +164 -0
  564. package/lib/testing/playwright/flaky-network-proxy.js.map +1 -0
  565. package/lib/testing/playwright/index.d.ts +1 -0
  566. package/lib/testing/playwright/index.d.ts.map +1 -1
  567. package/lib/testing/playwright/index.js +1 -0
  568. package/lib/testing/playwright/index.js.map +1 -1
  569. package/lib/testing/playwright/server-log-capture.d.ts +46 -16
  570. package/lib/testing/playwright/server-log-capture.d.ts.map +1 -1
  571. package/lib/testing/playwright/server-log-capture.js +111 -40
  572. package/lib/testing/playwright/server-log-capture.js.map +1 -1
  573. package/lib/testing/playwright/server-log-fixture.d.ts +16 -0
  574. package/lib/testing/playwright/server-log-fixture.d.ts.map +1 -1
  575. package/lib/testing/playwright/server-log-fixture.js +25 -6
  576. package/lib/testing/playwright/server-log-fixture.js.map +1 -1
  577. package/lib/testing/playwright/server-log-region.d.ts +15 -0
  578. package/lib/testing/playwright/server-log-region.d.ts.map +1 -0
  579. package/lib/testing/playwright/server-log-region.js +30 -0
  580. package/lib/testing/playwright/server-log-region.js.map +1 -0
  581. package/lib/testing/playwright/server-log-rename-reporter.d.ts.map +1 -1
  582. package/lib/testing/playwright/server-log-rename-reporter.js +4 -3
  583. package/lib/testing/playwright/server-log-rename-reporter.js.map +1 -1
  584. package/lib/testing/run-update-pipeline.d.ts +8 -5
  585. package/lib/testing/run-update-pipeline.d.ts.map +1 -1
  586. package/lib/testing/run-update-pipeline.js.map +1 -1
  587. package/lib/testing/stub-ast-document-manager.d.ts +11 -51
  588. package/lib/testing/stub-ast-document-manager.d.ts.map +1 -1
  589. package/lib/testing/stub-ast-document-manager.js +31 -59
  590. package/lib/testing/stub-ast-document-manager.js.map +1 -1
  591. package/lib/testing/stub-document-builder.d.ts +25 -11
  592. package/lib/testing/stub-document-builder.d.ts.map +1 -1
  593. package/lib/testing/stub-document-builder.js +91 -2
  594. package/lib/testing/stub-document-builder.js.map +1 -1
  595. package/lib/testing/stub-hydranium-text-documents.d.ts +33 -10
  596. package/lib/testing/stub-hydranium-text-documents.d.ts.map +1 -1
  597. package/lib/testing/stub-hydranium-text-documents.js +132 -16
  598. package/lib/testing/stub-hydranium-text-documents.js.map +1 -1
  599. package/lib/testing/stub-index-manager.js.map +1 -1
  600. package/lib/testing/stub-langium-documents.d.ts +3 -2
  601. package/lib/testing/stub-langium-documents.d.ts.map +1 -1
  602. package/lib/testing/stub-langium-documents.js.map +1 -1
  603. package/lib/testing/stub-model-service.d.ts +8 -7
  604. package/lib/testing/stub-model-service.d.ts.map +1 -1
  605. package/lib/testing/stub-model-service.js +4 -4
  606. package/lib/testing/stub-model-service.js.map +1 -1
  607. package/lib/testing/stub-project-manager.d.ts.map +1 -1
  608. package/lib/testing/stub-project-manager.js +10 -0
  609. package/lib/testing/stub-project-manager.js.map +1 -1
  610. package/lib/testing/stub-self-save-registry.d.ts +1 -1
  611. package/lib/testing/stub-self-save-registry.d.ts.map +1 -1
  612. package/lib/testing/stub-service-registry.d.ts +1 -1
  613. package/lib/testing/stub-service-registry.js.map +1 -1
  614. package/lib/testing/stub-writable-file-system.d.ts +1 -1
  615. package/lib/testing/stub-writable-file-system.d.ts.map +1 -1
  616. package/lib/testing/stub-writable-file-system.js +2 -1
  617. package/lib/testing/stub-writable-file-system.js.map +1 -1
  618. package/lib/util/connection-liveness.d.ts +22 -6
  619. package/lib/util/connection-liveness.d.ts.map +1 -1
  620. package/lib/util/connection-liveness.js +75 -15
  621. package/lib/util/connection-liveness.js.map +1 -1
  622. package/lib/util/environment.d.ts +7 -0
  623. package/lib/util/environment.d.ts.map +1 -1
  624. package/lib/util/environment.js +15 -1
  625. package/lib/util/environment.js.map +1 -1
  626. package/lib/util/registry.d.ts.map +1 -1
  627. package/package.json +41 -60
  628. package/src/browser/host-heads-in-worker.ts +218 -0
  629. package/src/browser/index.ts +17 -0
  630. package/src/browser/indexeddb-file-system-store.ts +192 -0
  631. package/src/browser/start-language-server-in-worker.ts +56 -0
  632. package/src/documents/ast-document-manager.ts +477 -250
  633. package/src/documents/client-ids.ts +5 -22
  634. package/src/documents/client-session-errors.ts +26 -0
  635. package/src/documents/client-session-registry.ts +262 -0
  636. package/src/documents/dirty-state-tracker.ts +167 -0
  637. package/src/documents/document-release-handler.ts +404 -0
  638. package/src/documents/document-release-scheduler.ts +121 -0
  639. package/src/documents/file-system-task-queue.ts +77 -0
  640. package/src/documents/hydranium-text-documents.ts +1044 -615
  641. package/src/documents/index.ts +10 -2
  642. package/src/documents/language-client-shadow.ts +501 -0
  643. package/src/documents/model-ledger.ts +66 -0
  644. package/src/documents/text-ledger.ts +112 -0
  645. package/src/documents/version-sync-service.ts +304 -0
  646. package/src/index.ts +3 -0
  647. package/src/langium/ast-extension/ast-extension-service.ts +77 -41
  648. package/src/langium/ast-extension/ast-node-builder.ts +35 -13
  649. package/src/langium/bootstrap.ts +79 -43
  650. package/src/langium/build-phase-pass/build-phase-pass-service.ts +29 -19
  651. package/src/langium/build-phase-pass/build-phase-pass.ts +37 -8
  652. package/src/langium/composite-ast-reflection.ts +112 -0
  653. package/src/langium/config/configuration-provider.ts +119 -0
  654. package/src/langium/config/index.ts +1 -0
  655. package/src/langium/diagnostics/console-tee.ts +150 -0
  656. package/src/langium/diagnostics/hydranium-langium-profiler.ts +15 -39
  657. package/src/langium/diagnostics/log-preamble.ts +1 -1
  658. package/src/langium/diagnostics/logger.ts +51 -17
  659. package/src/langium/diagnostics/lsp-logger.ts +83 -45
  660. package/src/langium/document-builder/build-pipeline-integration.ts +57 -32
  661. package/src/langium/document-builder/build-session.ts +4 -3
  662. package/src/langium/document-builder/document-builder.ts +773 -106
  663. package/src/langium/integration-services.ts +20 -23
  664. package/src/langium/integrity/integrity-rule.ts +22 -8
  665. package/src/langium/integrity/integrity-service.ts +656 -90
  666. package/src/langium/keys/containment.ts +3 -2
  667. package/src/langium/keys/name-based-key-provider.ts +24 -12
  668. package/src/langium/language-module.ts +52 -9
  669. package/src/langium/model-service/client-session.ts +769 -0
  670. package/src/langium/model-service/index.ts +2 -0
  671. package/src/langium/model-service/model-events.ts +82 -0
  672. package/src/langium/model-service/model-service.ts +885 -532
  673. package/src/langium/module.ts +141 -65
  674. package/src/langium/naming/name-provider.ts +34 -8
  675. package/src/langium/project/abstract-project-manager.ts +409 -20
  676. package/src/langium/project/project-change-event.ts +25 -3
  677. package/src/langium/project/project-manager.ts +31 -2
  678. package/src/langium/project/single-project-manager.ts +16 -1
  679. package/src/langium/residency/cst-residency-service.ts +210 -87
  680. package/src/langium/scope/ast-node-description-provider.ts +111 -16
  681. package/src/langium/scope/built-local-symbols.ts +13 -0
  682. package/src/langium/scope/fallback-scope.ts +38 -0
  683. package/src/langium/scope/hydranium-scope-computation.ts +45 -44
  684. package/src/langium/scope/hydranium-scope-provider.ts +245 -41
  685. package/src/langium/scope/index.ts +1 -0
  686. package/src/langium/scope/reference-candidate-provider.ts +6 -5
  687. package/src/langium/scope/scope-extension-service.ts +148 -56
  688. package/src/langium/scope/scoped-ast-node-description.ts +1 -3
  689. package/src/langium/serialization/abstract-serializer.ts +57 -41
  690. package/src/langium/serialization/json-serializer.ts +14 -3
  691. package/src/langium/serialization/serializer.ts +1 -1
  692. package/src/langium/serialization/yaml-serializer.ts +26 -4
  693. package/src/langium/service-registry.ts +41 -2
  694. package/src/langium/shared-services.ts +13 -4
  695. package/src/langium/single-langium-copy.ts +42 -0
  696. package/src/langium/transfer/transfer-encoder.ts +76 -50
  697. package/src/langium/trivia/comment-preserver.ts +1410 -0
  698. package/src/langium/trivia/document-ending-preserver.ts +71 -0
  699. package/src/langium/trivia/index.ts +14 -0
  700. package/src/langium/trivia/trivia-contribution.ts +39 -0
  701. package/src/langium/trivia/trivia-preserver.ts +56 -0
  702. package/src/langium/trivia/trivia-service.ts +99 -0
  703. package/src/langium/update-rewrite/update-rewrite-service.ts +1 -1
  704. package/src/langium/update-rewrite/update-rewrite.ts +1 -1
  705. package/src/langium/validation/document-validator.ts +312 -31
  706. package/src/langium/validation/validation-contribution-collector.ts +10 -1
  707. package/src/langium/workspace/ast-node-locator.ts +33 -0
  708. package/src/langium/workspace/document-uri-policy.ts +32 -18
  709. package/src/langium/workspace/file-not-found.ts +35 -0
  710. package/src/langium/workspace/file-system-provider.ts +258 -16
  711. package/src/langium/workspace/hydranium-langium-document-factory.ts +239 -4
  712. package/src/langium/workspace/hydranium-workspace-lock.ts +101 -2
  713. package/src/langium/workspace/hydranium-workspace-manager.ts +214 -35
  714. package/src/langium/workspace/in-memory-file-system-provider.ts +54 -23
  715. package/src/langium/workspace/index.ts +3 -0
  716. package/src/langium/workspace/initialize-workspace.ts +96 -4
  717. package/src/langium/workspace/langium-documents.ts +19 -1
  718. package/src/langium/workspace/persistent-file-system-provider.ts +27 -15
  719. package/src/{documents → langium/workspace}/self-save-registry.ts +12 -14
  720. package/src/langium/workspace/syntax-errors.ts +34 -0
  721. package/src/langium/workspace/virtual-document.ts +106 -25
  722. package/src/langium/workspace/write-lock-scope.ts +27 -12
  723. package/src/launcher/integrated-server.ts +3 -1
  724. package/src/locale/server-locale.ts +6 -6
  725. package/src/lsp/completion/hydranium-completion-provider.ts +82 -2
  726. package/src/lsp/connection-features.ts +67 -0
  727. package/src/lsp/diagnostics-connection.ts +56 -0
  728. package/src/lsp/edit-requests-guard.ts +152 -0
  729. package/src/lsp/hydranium-document-update-handler.ts +284 -110
  730. package/src/lsp/index.ts +6 -2
  731. package/src/lsp/lsp-latency.ts +62 -0
  732. package/src/lsp/save-text-document.ts +63 -0
  733. package/src/lsp/semantic-token-provider.ts +146 -8
  734. package/src/lsp/shared-module.ts +4 -8
  735. package/src/lsp/start-language-server.ts +21 -7
  736. package/src/messages/carriers.ts +4 -6
  737. package/src/messages/index.ts +11 -2
  738. package/src/messages/renderer.ts +11 -3
  739. package/src/node/event-loop-monitor.ts +3 -4
  740. package/src/node/heap-ceiling.ts +108 -0
  741. package/src/node/index.ts +5 -1
  742. package/src/node/latency-from-env.ts +1 -1
  743. package/src/node/log-file-sink.ts +6 -2
  744. package/src/node/log-preamble-node.ts +20 -10
  745. package/src/node/measure-memory.ts +5 -4
  746. package/src/node/memory-monitor.ts +10 -10
  747. package/src/node/node-file-system-provider.ts +214 -57
  748. package/src/node/profile-capture.ts +16 -15
  749. package/src/node/profiling-run.ts +30 -8
  750. package/src/node/rename-over-open-readers.ts +94 -0
  751. package/src/node/server-diagnostics.ts +1 -1
  752. package/src/node/server-state-snapshot.ts +28 -20
  753. package/src/node/socket-launcher.ts +78 -14
  754. package/src/node/startup-cpu-profile.ts +94 -0
  755. package/src/node/stdio-launcher.ts +29 -19
  756. package/src/node/validate-workspace.ts +8 -2
  757. package/src/testing/document-uri-policy-conformance.ts +1 -1
  758. package/src/testing/fake-document.ts +2 -1
  759. package/src/testing/index.ts +2 -1
  760. package/src/testing/langium-test-helpers.ts +1 -0
  761. package/src/testing/make-test-services.ts +83 -31
  762. package/src/testing/make-test-tracer.ts +26 -0
  763. package/src/testing/node/applied-edit-capture.ts +141 -0
  764. package/src/testing/node/index.ts +4 -1
  765. package/src/testing/node/lsp-harness.ts +2 -0
  766. package/src/testing/node/lsp-server-connection.ts +83 -119
  767. package/src/testing/node/scratch-workspace.ts +12 -6
  768. package/src/testing/node/settle-server.ts +89 -0
  769. package/src/testing/node/spawned-server.ts +57 -3
  770. package/src/testing/parse-semantic-root.ts +1 -1
  771. package/src/testing/playwright/flaky-network-proxy.ts +240 -0
  772. package/src/testing/playwright/index.ts +1 -0
  773. package/src/testing/playwright/server-log-capture.ts +137 -47
  774. package/src/testing/playwright/server-log-fixture.ts +46 -6
  775. package/src/testing/playwright/server-log-region.ts +32 -0
  776. package/src/testing/playwright/server-log-rename-reporter.ts +4 -3
  777. package/src/testing/run-update-pipeline.ts +8 -5
  778. package/src/testing/stub-ast-document-manager.ts +47 -123
  779. package/src/testing/stub-document-builder.ts +128 -15
  780. package/src/testing/stub-hydranium-text-documents.ts +184 -27
  781. package/src/testing/stub-langium-documents.ts +3 -2
  782. package/src/testing/stub-model-service.ts +9 -8
  783. package/src/testing/stub-project-manager.ts +10 -0
  784. package/src/testing/stub-self-save-registry.ts +1 -1
  785. package/src/testing/stub-service-registry.ts +1 -1
  786. package/src/testing/stub-writable-file-system.ts +3 -2
  787. package/src/util/connection-liveness.ts +81 -15
  788. package/src/util/environment.ts +24 -2
  789. package/lib/documents/language-client-text-shadow.d.ts +0 -124
  790. package/lib/documents/language-client-text-shadow.d.ts.map +0 -1
  791. package/lib/documents/language-client-text-shadow.js +0 -190
  792. package/lib/documents/language-client-text-shadow.js.map +0 -1
  793. package/lib/documents/self-save-registry.d.ts.map +0 -1
  794. package/lib/documents/self-save-registry.js.map +0 -1
  795. package/lib/lsp/instrument-connection.d.ts +0 -36
  796. package/lib/lsp/instrument-connection.d.ts.map +0 -1
  797. package/lib/lsp/instrument-connection.js +0 -62
  798. package/lib/lsp/instrument-connection.js.map +0 -1
  799. package/lib/node/process-memory.d.ts +0 -66
  800. package/lib/node/process-memory.d.ts.map +0 -1
  801. package/lib/node/process-memory.js +0 -252
  802. package/lib/node/process-memory.js.map +0 -1
  803. package/src/documents/language-client-text-shadow.ts +0 -211
  804. package/src/lsp/instrument-connection.ts +0 -66
  805. package/src/node/process-memory.ts +0 -299
@@ -7,16 +7,29 @@
7
7
  * SPDX-License-Identifier: MIT
8
8
  ********************************************************************************/
9
9
 
10
- import { isPromiseLike, Logger, type ProfileSession, type Tracer } from '@hydranium/protocol';
11
- import { type AstNode, AstUtils, DocumentState, interruptAndCheck, type LangiumDocument, UriUtils } from '@hydranium/langium';
12
- import { type CancellationToken, type Disposable } from 'vscode-languageserver';
10
+ import { describeErrorWithStack, isPromiseLike, Logger, type ProfileSession, textHash, type Tracer } from '@hydranium/protocol';
11
+ import {
12
+ type AstNode,
13
+ AstUtils,
14
+ DocumentState,
15
+ interruptAndCheck,
16
+ isMultiReference,
17
+ isOperationCancelled,
18
+ type LangiumDocument,
19
+ type URI,
20
+ UriUtils
21
+ } from '@hydranium/langium';
22
+ import { CancellationToken, type Disposable } from 'vscode-languageserver';
13
23
  import { type HydraniumDocumentBuilder } from '../document-builder/document-builder.js';
14
24
  import { type TextDocument } from 'vscode-languageserver-textdocument';
15
- import { type WritableFileSystemProvider } from '../../documents/ast-document-manager.js';
25
+ import { type WritableFileSystemProvider } from '../workspace/file-system-provider.js';
16
26
  import { type LogNameOptions } from '../diagnostics/logger.js';
17
27
  import { type HydraniumTextDocuments } from '../../documents/hydranium-text-documents.js';
18
28
  import { Registry } from '../../util/registry.js';
19
29
  import { type ServerLanguageServices } from '../language-module.js';
30
+ import { type ServerSharedServices } from '../module.js';
31
+ import { hasSkippedText } from '../workspace/syntax-errors.js';
32
+ import { isVirtualUri } from '../workspace/virtual-document.js';
20
33
  import { type IntegrityRuleRegistry } from './integrity-contribution.js';
21
34
  import { IntegrityPhase, type IntegrityRule, type IntegritySyncMode } from './integrity-rule.js';
22
35
 
@@ -75,11 +88,21 @@ export namespace IntegrityService {
75
88
  * at `DocumentState.Validated` and delivered separately via `publishDiagnostics`
76
89
  * or the `onModelUpdated` event.
77
90
  *
91
+ * A document whose parse lacks some of its text reaches this state
92
+ * unrepaired: no rule runs on it until it parses.
93
+ *
78
94
  * The AST is what this guarantees, not the text. For a closed document whose
79
95
  * repair was STAGED rather than written (`'editor'` sync mode),
80
96
  * `textDocument` still mirrors disk — read the repair off the AST, or from
81
97
  * the staged content the next open consumes.
82
98
  *
99
+ * A `Linked`-phase repair settles the documents that reference the repaired
100
+ * one in the same build against what the repair replaced. A build after it
101
+ * relinks them, and they settle again. Past `maxRelinkChain` such builds in
102
+ * a row they keep stale links. A closed `'editor'` document whose CST was
103
+ * shed re-reads its unrepaired text in that build and repeats the repair,
104
+ * so they resolve a renamed element by its old name until the next change.
105
+ *
83
106
  * Invariant: integrity rules only register at Parsed or Linked, so the build
84
107
  * is guaranteed post-integrity once it advances past `onBuildPhase(Linked)`
85
108
  * into `IndexedReferences`. {@link IntegrityService.register} enforces this at
@@ -89,6 +112,17 @@ export namespace IntegrityService {
89
112
  export const SettledState: DocumentState = DocumentState.IndexedReferences;
90
113
  }
91
114
 
115
+ /**
116
+ * The shared services of each server that has logged its first silent
117
+ * write-back. Every language of a server holds the same shared services, so
118
+ * keying on them makes {@link DefaultIntegrityService.noteSilentWrite} report once
119
+ * per server rather than once per language.
120
+ */
121
+ const silentWriteReported = new WeakSet<ServerSharedServices>();
122
+
123
+ /** The running builds that relink dependents, per server, because a repair in one language can extend a chain in another. */
124
+ const runningRelinksByServer = new WeakMap<ServerSharedServices, Set<DependentRelink>>();
125
+
92
126
  /**
93
127
  * Construction options for {@link DefaultIntegrityService}. All fields
94
128
  * optional — the defaults reproduce the framework's behaviour exactly.
@@ -102,6 +136,26 @@ export interface IntegrityServiceOptions extends LogNameOptions {
102
136
  * to `'silent'`. See {@link IntegritySyncMode}.
103
137
  */
104
138
  readonly syncMode?: IntegritySyncMode;
139
+ /**
140
+ * How many builds in a row may relink the dependents of `Linked`-phase
141
+ * repairs before a repair only warns. A rule that settles within it gets
142
+ * every build it needs. An integer of at least 1, 3 by default.
143
+ */
144
+ readonly maxRelinkChain?: number;
145
+ }
146
+
147
+ /** The text a `Linked`-phase repair was computed from, and the text it produced. */
148
+ export interface IntegrityRepairTexts {
149
+ readonly from: string;
150
+ readonly to: string;
151
+ }
152
+
153
+ /** The build a repaired document has requested to relink its dependents. */
154
+ export interface DependentRelink {
155
+ /** The repaired document, whose dependents the build relinks. */
156
+ readonly document: LangiumDocument;
157
+ /** Its place in a chain of such builds, each requested by a repair of a document the one before relinked. */
158
+ readonly chain: number;
105
159
  }
106
160
 
107
161
  /**
@@ -128,15 +182,16 @@ export class DefaultIntegrityService<TRoot extends AstNode = AstNode> implements
128
182
  protected ruleLoggers = new Map<string, Logger>();
129
183
 
130
184
  /**
131
- * Rules bucketed by the phase they target, preserving the registry's
132
- * priority order within each bucket. Rebuilt lazily on the first
133
- * `enforceIntegrity` call after a registry mutation — staleness is detected
134
- * via reference identity of the registry's cached array (see
135
- * {@link Registry.all}). Filtering the rule list per call would instead
136
- * allocate one transient array per document per phase, which a workspace
137
- * whose documents all settle at the same phase pays on every build.
185
+ * Rules bucketed by the phase they target, then by each `$type` from
186
+ * {@link nodeTypesFor}, preserving the registry's priority order within
187
+ * each bucket. Rebuilt lazily on the first `enforceIntegrity` call after a
188
+ * registry mutation — staleness is detected via reference identity of the
189
+ * registry's cached array (see {@link Registry.all}). Filtering the rule
190
+ * list per call would instead allocate one transient array per document per
191
+ * phase, which a workspace whose documents all settle at the same phase pays
192
+ * on every build.
138
193
  */
139
- protected phaseBuckets = new Map<DocumentState, readonly IntegrityRule[]>();
194
+ protected phaseBuckets = new Map<DocumentState, ReadonlyMap<string, readonly IntegrityRule[]>>();
140
195
  protected phaseBucketsFor: readonly IntegrityRule[] | undefined;
141
196
 
142
197
  /** Controls how corrections are persisted for closed files. See {@link IntegritySyncMode}. */
@@ -147,11 +202,28 @@ export class DefaultIntegrityService<TRoot extends AstNode = AstNode> implements
147
202
  protected readonly tracer: Tracer;
148
203
  /**
149
204
  * The framework document builder, used to re-parse + re-link a document in place after a
150
- * Linked-phase correction (see {@link resyncDocument}). The framework binds
151
- * `shared.workspace.DocumentBuilder` to {@link HydraniumDocumentBuilder}; the narrowing
152
- * cast surfaces its single-document phase runner.
205
+ * Linked-phase correction (see {@link resyncDocument}).
153
206
  */
154
207
  protected readonly documentBuilder: HydraniumDocumentBuilder;
208
+ /** Each document's {@link DependentRelink}, from its request until it settles. See {@link relinkDependents}. */
209
+ protected readonly relinkBuilds = new WeakMap<LangiumDocument, DependentRelink>();
210
+ /** The {@link replayKey} of the latest repair of each document that {@link stagesRepairs}, with the hash of the text it came from. */
211
+ protected readonly replayKeys = new WeakMap<LangiumDocument, { readonly key: string; readonly fromHash: string }>();
212
+ /** The relinks the check inside the lock found needed, until their build starts. */
213
+ protected readonly scheduledRelinks = new WeakSet<DependentRelink>();
214
+ /** The relinks of every language whose build runs, from its start until it settles or the next build starts. */
215
+ protected readonly runningRelinks: Set<DependentRelink>;
216
+ /** The rules of a repair while a relink's build waited or ran. Only one while it ran gets the next build in the chain. */
217
+ protected readonly repairedAgain = new WeakMap<DependentRelink, readonly IntegrityRule[]>();
218
+ /**
219
+ * Marks {@link scheduledRelinks} running as their builds start. Set on the
220
+ * first request only so it subscribes once, and never disposed.
221
+ */
222
+ protected relinkStarts?: Disposable;
223
+ /** See {@link IntegrityServiceOptions.maxRelinkChain}. */
224
+ protected readonly maxRelinkChain: number;
225
+ /** The rules that mutated each document in its latest `Linked`-phase {@link enforceIntegrity} pass, so a warning can name them. */
226
+ protected readonly mutatingRules = new WeakMap<LangiumDocument, Set<IntegrityRule>>();
155
227
 
156
228
  constructor(
157
229
  protected readonly services: ServerLanguageServices,
@@ -160,8 +232,16 @@ export class DefaultIntegrityService<TRoot extends AstNode = AstNode> implements
160
232
  this.textDocuments = services.shared.workspace.TextDocuments;
161
233
  this.fileSystemProvider = services.shared.workspace.FileSystemProvider;
162
234
  this.tracer = services.shared.Tracer.for(options.logName ?? 'Integrity').trace('instantiated');
163
- this.documentBuilder = services.shared.workspace.DocumentBuilder as HydraniumDocumentBuilder;
235
+ this.documentBuilder = services.shared.workspace.DocumentBuilder;
164
236
  this.syncMode = options.syncMode ?? 'silent';
237
+ this.maxRelinkChain = options.maxRelinkChain ?? 3;
238
+ if (!Number.isInteger(this.maxRelinkChain) || this.maxRelinkChain < 1) {
239
+ throw new RangeError(
240
+ `The integrity service's maxRelinkChain option must be an integer of at least 1, not ${this.maxRelinkChain}.`
241
+ );
242
+ }
243
+ this.runningRelinks = runningRelinksByServer.get(services.shared) ?? new Set();
244
+ runningRelinksByServer.set(services.shared, this.runningRelinks);
165
245
 
166
246
  // Read the language's IntegrityRuleContribution group and let each
167
247
  // contribution register one or many rules through this service (the
@@ -189,23 +269,32 @@ export class DefaultIntegrityService<TRoot extends AstNode = AstNode> implements
189
269
  * rule set to completion against a doomed build.
190
270
  */
191
271
  async enforceBatch(documents: readonly LangiumDocument[], phase: DocumentState, cancelToken: CancellationToken): Promise<void> {
192
- const phaseStart = performance.now();
272
+ const sincePhaseStart = this.services.shared.Clock.stopwatch();
193
273
  let mutatedCount = 0;
194
274
  // Per-rule self-time profiling is opt-in: only spin up a session when the
195
275
  // debug threshold is active, so the production `info` path allocates
196
276
  // nothing and the hot rule loop stays a single const-branch off `session`.
197
277
  const session = Logger.isLevelEnabled('debug') ? this.tracer.profile(`integrity ${DocumentState[phase]}`) : undefined;
278
+ // Before any skip or cancel, so text with parse errors also ends a replay.
198
279
  for (const document of documents) {
199
- const changed = await this.enforceIntegrity(document, phase, cancelToken, session);
200
- if (changed) {
280
+ this.forgetReplayOfOtherText(document);
281
+ }
282
+ for (const document of documents) {
283
+ if (this.isStructurallyBroken(document)) {
284
+ this.tracer.with(document.uri.toString()).debug('Parse errors: rules skipped until the document parses');
285
+ } else if (await this.enforceIntegrity(document, phase, cancelToken, session)) {
201
286
  mutatedCount++;
202
- await this.resyncDocument(document, cancelToken);
287
+ // Not cancellable: see `resyncDocument`.
288
+ await this.resyncDocument(document, CancellationToken.None);
203
289
  }
204
290
  await interruptAndCheck(cancelToken);
205
291
  }
206
- const totalMs = Math.round(performance.now() - phaseStart);
207
- if (totalMs >= 25 || mutatedCount > 0) {
292
+ const totalMs = Math.round(sincePhaseStart.elapsedMs);
293
+ // A repair is the operator's to know of; the timing alone is diagnosis.
294
+ if (mutatedCount > 0) {
208
295
  this.tracer.info(`enforce ${DocumentState[phase]}: docs=${documents.length}, mutated=${mutatedCount} [${totalMs}ms]`);
296
+ } else if (totalMs >= 25) {
297
+ this.tracer.debug(`enforce ${DocumentState[phase]}: docs=${documents.length}, mutated=${mutatedCount} [${totalMs}ms]`);
209
298
  }
210
299
  // One line per rule (count + self-% + self-ms), sorted by cost — only when profiling.
211
300
  session?.report('debug');
@@ -224,6 +313,14 @@ export class DefaultIntegrityService<TRoot extends AstNode = AstNode> implements
224
313
  );
225
314
  }
226
315
  const disposable = this.rules.register(rule);
316
+ // No parsed node carries a type the reflection does not know, so such a
317
+ // rule never runs. A warning, not a throw: a test reflection may omit the type.
318
+ if (!this.services.shared.AstReflection.getAllTypes().includes(rule.nodeType)) {
319
+ this.tracer.warn(
320
+ `Integrity rule '${this.formatRule(rule)}' is keyed on node type '${rule.nodeType}', ` +
321
+ 'which the AST reflection does not know, so no parsed node matches it.'
322
+ );
323
+ }
227
324
  this.ruleLoggers.set(rule.id, this.tracer.sub(IntegrityPhase.toString(rule.phase)).sub(rule.label ?? rule.id));
228
325
  return {
229
326
  dispose: () => {
@@ -242,6 +339,15 @@ export class DefaultIntegrityService<TRoot extends AstNode = AstNode> implements
242
339
  return removed;
243
340
  }
244
341
 
342
+ /**
343
+ * Seam: does the parse of `document` lack some of its text? {@link enforceBatch}
344
+ * runs no rule on such a document, because a repair is written back by
345
+ * serializing the AST, and a recovered AST lacks what the parser skipped.
346
+ */
347
+ protected isStructurallyBroken(document: LangiumDocument): boolean {
348
+ return hasSkippedText(document);
349
+ }
350
+
245
351
  /**
246
352
  * Run all rules for the given phase against matching AST nodes. Returns
247
353
  * `true` if any rule mutated the AST.
@@ -260,8 +366,10 @@ export class DefaultIntegrityService<TRoot extends AstNode = AstNode> implements
260
366
  * full rule sweep) — coarser than per (node × rule) to avoid a microtask
261
367
  * per inner iteration, finer than per-document so a long-running rule set
262
368
  * on a large file can still interrupt mid-stream. Throws
263
- * `OperationCancelled` when the token is cancelled; Langium's build
264
- * pipeline absorbs the throw at the lock-holder.
369
+ * `OperationCancelled` when the token is cancelled before any rule mutated
370
+ * the AST; Langium's build pipeline absorbs the throw at the lock-holder.
371
+ * After a mutation it stops the sweep and returns `true` instead, so the
372
+ * caller can resync what was mutated before the cancellation unwinds.
265
373
  */
266
374
  async enforceIntegrity(
267
375
  document: LangiumDocument,
@@ -273,29 +381,47 @@ export class DefaultIntegrityService<TRoot extends AstNode = AstNode> implements
273
381
  return false;
274
382
  }
275
383
  let changed = false;
276
- const rulesForPhase = this.getPhaseBucket(phase);
277
- if (rulesForPhase.length === 0) {
384
+ const rulesByType = this.getPhaseBucket(phase);
385
+ if (rulesByType.size === 0) {
278
386
  return false;
279
387
  }
388
+ // Read only after a Linked-phase repair. Cleared when such a pass starts, so
389
+ // one that records nothing leaves no older record to be named in its place.
390
+ const recorded = phase > DocumentState.Parsed;
391
+ if (recorded) {
392
+ this.mutatingRules.delete(document);
393
+ }
394
+ let mutating: Set<IntegrityRule> | undefined;
280
395
  const relativeUri = this.services.shared.workspace.WorkspaceManager.wsRelativePath(document.uri);
281
396
  for (const node of AstUtils.streamAst(document.parseResult.value)) {
282
- for (const rule of rulesForPhase) {
283
- if (node.$type === rule.nodeType) {
284
- const ruleLogger = this.ruleLoggers.get(rule.id)!.with(relativeUri);
285
- // Scope per rule so the session aggregates self-time by rule id across all
286
- // nodes; off the profiling path `session` is undefined and we call directly.
287
- const enforced = session
288
- ? session.scope(rule.id, () => rule.enforce(node, document, ruleLogger))
289
- : rule.enforce(node, document, ruleLogger);
290
- const mutated = isPromiseLike(enforced) ? await enforced : enforced;
291
- if (mutated) {
292
- ruleLogger.trace(`[${relativeUri}] Mutated ${this.formatNode(node)}`);
397
+ for (const rule of rulesByType.get(node.$type) ?? NO_RULES) {
398
+ const ruleLogger = this.ruleLoggers.get(rule.id)!.with(relativeUri);
399
+ // Scope per rule so the session aggregates self-time by rule id across all
400
+ // nodes; off the profiling path `session` is undefined and we call directly.
401
+ const enforced = session
402
+ ? session.scope(rule.id, () => rule.enforce(node, document, ruleLogger))
403
+ : rule.enforce(node, document, ruleLogger);
404
+ const mutated = isPromiseLike(enforced) ? await enforced : enforced;
405
+ if (mutated) {
406
+ if (recorded && mutating === undefined) {
407
+ // Only a mutating pass leads to a read, so a pass that mutates nothing allocates nothing.
408
+ mutating = new Set();
409
+ this.mutatingRules.set(document, mutating);
293
410
  }
294
- changed = mutated || changed;
411
+ mutating?.add(rule);
412
+ ruleLogger.trace(`[${relativeUri}] Mutated ${this.formatNode(node)}`);
295
413
  }
414
+ changed = mutated || changed;
296
415
  }
297
416
  if (cancelToken !== undefined) {
298
- await interruptAndCheck(cancelToken);
417
+ try {
418
+ await interruptAndCheck(cancelToken);
419
+ } catch (err: unknown) {
420
+ if (changed && isOperationCancelled(err)) {
421
+ return true;
422
+ }
423
+ throw err;
424
+ }
299
425
  }
300
426
  }
301
427
  return changed;
@@ -305,17 +431,21 @@ export class DefaultIntegrityService<TRoot extends AstNode = AstNode> implements
305
431
  * Re-serialise the document text after AST mutations, propagate corrections,
306
432
  * and re-parse at Parsed phase (safe because linking hasn't occurred yet).
307
433
  *
308
- * `cancelToken` is checked at entry so a preempted build skips the
309
- * serialise / disk-write atomic; the steps inside resync are not further
310
- * subdivided because each is a single fast operation that does not yield
311
- * a natural interruption point.
434
+ * Runs to the end whatever `cancelToken` says: the AST is already mutated,
435
+ * and skipping the resync leaves it against unchanged text that the next
436
+ * build does not re-parse. `cancelToken` reaches only the re-parse.
312
437
  */
313
438
  protected async resyncDocument(document: LangiumDocument, cancelToken?: CancellationToken): Promise<void> {
314
- if (cancelToken?.isCancellationRequested) {
315
- return;
316
- }
317
439
  const root = document.parseResult.value as TRoot;
318
- const newText = await this.services.serializer.Serializer.serializeAst(root);
440
+ // Extracted while `document` still holds the text being replaced — the
441
+ // resync that follows overwrites it with `newText`. Absent on a test
442
+ // stub that binds no trivia group; `createServerLanguageModule` always
443
+ // provides it, and an empty registry is already the no-op.
444
+ const trivia = this.services.trivia?.TriviaService;
445
+ const extracted = trivia?.extract(document);
446
+ const serialized = await this.services.serializer.Serializer.serializeAst(root);
447
+ const newText =
448
+ extracted === undefined ? serialized : trivia!.apply(serialized, extracted, UriUtils.toUri(document.textDocument.uri));
319
449
 
320
450
  const oldText = document.textDocument.getText();
321
451
  if (oldText === newText) {
@@ -323,13 +453,98 @@ export class DefaultIntegrityService<TRoot extends AstNode = AstNode> implements
323
453
  }
324
454
 
325
455
  const version = document.textDocument.version;
326
- // Keep the same version so HydraniumTextDocuments doesn't reject subsequent client edits.
327
- // Use manager.update so the `instanceof FullTextDocument` gate in the bare
328
- // `TextDocument.update` doesn't trip on adopter-custom text-document types.
329
- const textDocument = this.textDocuments.update(document.textDocument, [{ text: newText }], version);
456
+ // Commit into the store's own document when the URI is open there, rather
457
+ // than into whichever text-document object this build is holding. The two
458
+ // are the same on the LSP path and are NOT when the document was built for
459
+ // an already-open URI through `fromString`: repairing only the build's copy
460
+ // leaves the store — the server's authority on what an open document says —
461
+ // on the unrepaired text, and the reconciling re-parse then redefines
462
+ // `textDocument` onto the store's object and discards the repair with it.
463
+ //
464
+ // Compared against the CST's own text, not against `document.textDocument`.
465
+ // That object IS the store's on the LSP path, so asking it whether the
466
+ // store still agrees compares the store with itself and can only ever say
467
+ // yes — the guard would be vacuous exactly where an editor race can happen.
468
+ // The CST's `fullText` is the text that actually produced this AST, which
469
+ // is the only thing a repair derived from it is valid against. A document
470
+ // with no CST (built from a model rather than parsed) has no better answer
471
+ // than the text it is carrying.
472
+ const parsedFrom = document.parseResult.value.$cstNode?.root.fullText ?? oldText;
473
+ const commit = this.textDocuments.commitRepair(document.textDocument.uri, parsedFrom, newText);
474
+ if (commit.status === 'stale') {
475
+ // An edit landed after the parse this repair came from, so the repair
476
+ // describes text the store has already replaced. Abandon it rather than
477
+ // hand it to `syncCorrections`: an override that persists from there
478
+ // would write a correction over the edit that superseded it. That edit
479
+ // drives a build of its own, which re-runs these rules against what the
480
+ // document actually says.
481
+ //
482
+ // Abandoning the WRITE is only half of it. The build carries on to its
483
+ // settled phase with whatever AST is registered, and every settled
484
+ // consumer reads that AST rather than the store — so leaving the rule's
485
+ // mutation in place would answer questions about this document from a
486
+ // parse of text nobody has. Reconciling discards it by re-reading the
487
+ // store, which is what the document now has to agree with.
488
+ //
489
+ // No dependents are relinked, because the edit's build relinks them.
490
+ await this.reconcileDocument(document, cancelToken);
491
+ return;
492
+ }
493
+
494
+ // A committed repair is a new version; a document no client has open keeps
495
+ // its number here, and the re-versioning below moves it once the build
496
+ // has reconciled. Through the store's `update`, so the `instanceof
497
+ // FullTextDocument` gate in the bare `TextDocument.update` does not trip
498
+ // on adopter-custom text-document types.
499
+ const textDocument =
500
+ commit.status === 'committed' ? commit.document : this.textDocuments.update(document.textDocument, [{ text: newText }], version);
330
501
 
331
- await this.syncCorrections(textDocument);
502
+ await this.syncCorrections(textDocument, parsedFrom);
332
503
 
504
+ if (document.state > DocumentState.Parsed) {
505
+ // Requested before the in-place relink, and an error logged, so neither skips the other.
506
+ try {
507
+ this.relinkDependents(document, { from: parsedFrom, to: newText });
508
+ } catch (error: unknown) {
509
+ this.tracer
510
+ .with(document.uri.toString())
511
+ .error(`Relinking the dependents of a repaired document failed. ${describeErrorWithStack(error)}`);
512
+ }
513
+ }
514
+ await this.reconcileDocument(document, cancelToken);
515
+
516
+ // Re-version against the REPAIRED text, after either branch. The repair is
517
+ // a content change the store has not seen: whatever reconciliation ran
518
+ // during the re-parse saw the text on disk, which in `'editor'` sync mode
519
+ // is the PRE-repair text. Leave the sequence describing that and the next
520
+ // open hashes the repair, finds a mismatch and steps the version again, so
521
+ // every base version taken from this build is stale before it is used.
522
+ // An open document keeps the store's own version, which is not this
523
+ // method's to move.
524
+ //
525
+ // Deliberately NOT gated on cancellation, unlike the entry to this method:
526
+ // `syncCorrections` has already written or staged the repair by now, so a
527
+ // preempted build that skipped this would leave the sequence describing
528
+ // text that is no longer there.
529
+ if (!this.textDocuments.isOpen(document.textDocument.uri)) {
530
+ // The root describes the repair too; left at the re-parse's number, a
531
+ // syncing read waits for a build nothing requests. The CST is still
532
+ // the unrepaired text, so the repair is reported as the parsed text.
533
+ this.services.shared.workspace.VersionSyncService.modelProduced(document, { text: newText });
534
+ }
535
+ }
536
+
537
+ /**
538
+ * Bring `document` back into agreement with the text it is supposed to
539
+ * describe, after a rule has mutated its AST.
540
+ *
541
+ * Used for a correction that was applied and for one that was abandoned,
542
+ * because the document is left inconsistent either way: applied, the text
543
+ * moved under the CST; abandoned, the AST carries a mutation the text never
544
+ * had. Both are the same question — re-read and re-derive — and the phase is
545
+ * what decides how far that has to go.
546
+ */
547
+ protected async reconcileDocument(document: LangiumDocument, cancelToken?: CancellationToken): Promise<void> {
333
548
  if (document.state <= DocumentState.Parsed) {
334
549
  // Parsed-phase correction: re-parse only. The build pipeline still runs
335
550
  // IndexedContent → ComputedScopes → Linked → … on the fresh AST afterwards,
@@ -354,25 +569,231 @@ export class DefaultIntegrityService<TRoot extends AstNode = AstNode> implements
354
569
  // stays in the builder, its proper home, rather than being duplicated here.
355
570
  await this.documentBuilder.reparseAndRelink(document, cancelToken);
356
571
  }
572
+ }
357
573
 
358
- // Re-version against the REPAIRED text, after either branch. The repair is
359
- // a content change the store has not seen: whatever reconciliation ran
360
- // during the re-parse saw the text on disk, which in `'editor'` sync mode
361
- // is the PRE-repair text. Leave the sequence describing that and the next
362
- // open hashes the repair, finds a mismatch and steps the version again, so
363
- // every `baseVersion` taken from this build is stale before it is used.
364
- // Falls back to the pre-re-parse number for a URI the store never tracked,
365
- // where there is no sequence to advance; an OPEN document answers
366
- // `undefined` and keeps the store's own version, which is not this
367
- // method's to move.
368
- //
369
- // Deliberately NOT gated on cancellation, unlike the entry to this method:
370
- // `syncCorrections` has already written or staged the repair by now, so a
371
- // preempted build that skipped this would leave the sequence describing
372
- // text that is no longer there.
373
- const reconciled = this.textDocuments.reconcileExternalContent(document.textDocument.uri, newText) ?? version;
374
- if (document.textDocument.version !== reconciled) {
375
- this.textDocuments.update(document.textDocument, [], reconciled);
574
+ /**
575
+ * Relink the documents that reference `document` in a build after this
576
+ * one, where it counts as changed. They were linked before the repair, so
577
+ * they hold nodes a re-parse replaced, or names the repair changed.
578
+ *
579
+ * Each document has at most one such build, its {@link DependentRelink},
580
+ * which goes through four steps.
581
+ *
582
+ * 1. Requested here, and kept in {@link relinkBuilds}. A repair while it
583
+ * waits is relinked by it.
584
+ * 2. Checked inside the lock, which skips it unless {@link isRelinkNeeded}.
585
+ * A needed one joins {@link scheduledRelinks}. While a build outside the
586
+ * lock runs, it is requested again once that build ends.
587
+ * 3. Started, when `onUpdate` reports its build. An explicit `build` starts
588
+ * none. It is in {@link runningRelinks} until it settles or the next
589
+ * build starts that overlaps no other. A repair while it runs is kept in
590
+ * {@link repairedAgain} and gets the next build.
591
+ * 4. Settled, when its request resolves, which drops it from every record.
592
+ *
593
+ * A repair of a document that a running build relinked continues that
594
+ * build's chain, which {@link maxRelinkChain} bounds. A repair with the
595
+ * {@link replayKey} of the one before requests none.
596
+ */
597
+ protected relinkDependents(document: LangiumDocument, repair: IntegrityRepairTexts): void {
598
+ const key = this.replayKey(document, repair);
599
+ if (key !== undefined && this.replayKeys.get(document)?.key === key) {
600
+ return;
601
+ }
602
+ const rules = [...(this.mutatingRules.get(document) ?? [])];
603
+ const pending = this.relinkBuilds.get(document);
604
+ let requested = true;
605
+ if (pending !== undefined) {
606
+ this.repairedAgain.set(pending, rules);
607
+ } else {
608
+ const causes = [...this.runningRelinks].filter(relink => this.isRelinkedBy(document, relink.document));
609
+ requested = this.relinkInChain(document, Math.max(0, ...causes.map(relink => relink.chain)) + 1, rules);
610
+ }
611
+ // Recorded after the request, so one that throws or is past the bound leaves no key that skips the next repair.
612
+ if (key !== undefined && requested) {
613
+ this.replayKeys.set(document, { key, fromHash: textHash(repair.from) });
614
+ }
615
+ }
616
+
617
+ /**
618
+ * Whether a build where `changed` counts as changed relinks `document`, as
619
+ * Langium's `shouldRelink` decides. Read from the document's links, because
620
+ * its build has dropped its indexed references until it indexes again.
621
+ */
622
+ protected isRelinkedBy(document: LangiumDocument, changed: LangiumDocument): boolean {
623
+ const target = changed.uri.toString();
624
+ return document.references.some(reference => {
625
+ const descriptions = isMultiReference(reference)
626
+ ? reference.items.map(item => item.$nodeDescription)
627
+ : [reference.$nodeDescription];
628
+ return reference.error !== undefined || descriptions.some(description => description?.documentUri.toString() === target);
629
+ });
630
+ }
631
+
632
+ /**
633
+ * Request the build at `chain`, or warn naming `rules` past
634
+ * {@link maxRelinkChain} and drop the {@link replayKey} of `document`.
635
+ * Returns whether it requested the build.
636
+ */
637
+ protected relinkInChain(document: LangiumDocument, chain: number, rules: readonly IntegrityRule[]): boolean {
638
+ if (chain > this.maxRelinkChain) {
639
+ this.replayKeys.delete(document);
640
+ const builds = this.maxRelinkChain === 1 ? '1 build in a row that relinks' : `${this.maxRelinkChain} builds in a row that relink`;
641
+ this.tracer
642
+ .with(document.uri.toString())
643
+ .warn(
644
+ `${this.formatRules(rules)} repaired documents in ${builds} their dependents, ` +
645
+ 'which keep stale links until the next change. A rule that does not settle causes this, ' +
646
+ 'or documents with linking errors that rules repair one after another.'
647
+ );
648
+ return false;
649
+ }
650
+ this.requestRelink(document, chain);
651
+ return true;
652
+ }
653
+
654
+ /** Request the build {@link relinkInChain} decided on, skipped unless {@link isRelinkNeeded}. */
655
+ protected requestRelink(document: LangiumDocument, chain: number): void {
656
+ const uri = document.uri;
657
+ this.relinkStarts ??= this.documentBuilder.onUpdate(changed => this.startRelinks(changed));
658
+ const relink: DependentRelink = { document, chain };
659
+ let unlockedBuild: Promise<void> | undefined;
660
+ const stillNeeded = (): boolean => {
661
+ // No locked build runs during the lock read, so one seen here runs outside the lock.
662
+ unlockedBuild = this.documentBuilder.activeBuild;
663
+ if (unlockedBuild !== undefined) {
664
+ return false;
665
+ }
666
+ // Marked before the deferral check, so a deferred relink stays scheduled until it settles.
667
+ const needed = this.isRelinkNeeded(document);
668
+ if (needed) {
669
+ this.scheduledRelinks.add(relink);
670
+ }
671
+ return needed;
672
+ };
673
+ const settled = (): void => {
674
+ this.relinkBuilds.delete(document);
675
+ this.scheduledRelinks.delete(relink);
676
+ this.runningRelinks.delete(relink);
677
+ };
678
+ const requestFailed = (error: unknown): void => {
679
+ // No build relinks the dependents, so the same repair again needs one.
680
+ this.replayKeys.delete(document);
681
+ this.tracer
682
+ .with(uri.toString())
683
+ .error(`Requesting the next build that relinks the dependents of a repaired document failed. ${describeErrorWithStack(error)}`);
684
+ };
685
+ const request = this.services.shared.workspace.VersionSyncService.requestRecoveryBuild(uri, { stillNeeded });
686
+ // Recorded after the request, so one that throws leaves no relink that never settles.
687
+ this.relinkBuilds.set(document, relink);
688
+ request
689
+ .then(
690
+ () => {
691
+ if (unlockedBuild !== undefined) {
692
+ // Requested again once that build ends, so the two never overlap. A repair meanwhile waits for it.
693
+ unlockedBuild
694
+ .then(() => {
695
+ settled();
696
+ this.requestRelink(document, chain);
697
+ })
698
+ .catch(requestFailed);
699
+ return;
700
+ }
701
+ // A build that never started, or was cancelled and not run again,
702
+ // leaves the repair to the build that held it back. Read before the
703
+ // next build's `onUpdate` clears it, which the lock's queue orders later.
704
+ const ran = this.runningRelinks.has(relink);
705
+ settled();
706
+ const again = this.repairedAgain.get(relink);
707
+ if (ran && again !== undefined) {
708
+ this.relinkInChain(document, chain + 1, again);
709
+ }
710
+ },
711
+ (error: unknown) => {
712
+ settled();
713
+ // The dependents were not relinked, so the same repair again needs its build.
714
+ this.replayKeys.delete(document);
715
+ this.tracer
716
+ .with(uri.toString())
717
+ .error(`The build that relinks the dependents of a repaired document failed. ${describeErrorWithStack(error)}`);
718
+ }
719
+ )
720
+ .catch(requestFailed);
721
+ }
722
+
723
+ /** Mark the {@link scheduledRelinks} of the `changed` documents running, as the build that carries them starts. */
724
+ protected startRelinks(changed: readonly URI[]): void {
725
+ // An explicit build relinks no dependents and ends no build it overlaps.
726
+ if (this.documentBuilder.isInsideBuildUpdate()) {
727
+ return;
728
+ }
729
+ // The build before this one ended, done or cancelled, so its relinks run no more,
730
+ // unless this one overlaps it, as a reentrant build does.
731
+ // The set is shared by every language. Each service clears only its own,
732
+ // because another's listener may already have marked this build's relinks running.
733
+ if (!this.documentBuilder.overlapsRunningBuild()) {
734
+ for (const relink of this.runningRelinks) {
735
+ if (this.relinkBuilds.get(relink.document) === relink) {
736
+ this.runningRelinks.delete(relink);
737
+ }
738
+ }
739
+ }
740
+ const documents = this.services.shared.workspace.LangiumDocuments;
741
+ for (const uri of changed) {
742
+ const document = documents.getDocument(uri);
743
+ const relink = document === undefined ? undefined : this.relinkBuilds.get(document);
744
+ if (relink !== undefined && this.scheduledRelinks.delete(relink)) {
745
+ // The starting build relinks any repair while it waited.
746
+ this.repairedAgain.delete(relink);
747
+ this.runningRelinks.add(relink);
748
+ }
749
+ }
750
+ }
751
+
752
+ /**
753
+ * Whether a build where `document` counts as changed relinks another
754
+ * document, by the builder's own test, which counts a document a cancelled
755
+ * build left unindexed as relinked.
756
+ */
757
+ protected isRelinkNeeded(document: LangiumDocument): boolean {
758
+ const documents = this.services.shared.workspace.LangiumDocuments;
759
+ const changed = new Set([document.uri.toString()]);
760
+ return (
761
+ documents.getDocument(document.uri) === document &&
762
+ documents.all.some(other => other !== document && this.documentBuilder.shouldRelink(other, changed))
763
+ );
764
+ }
765
+
766
+ /**
767
+ * Whether `document` stages its repairs rather than writing them, as a
768
+ * closed document does in `'editor'` mode, so its source keeps the
769
+ * unrepaired text.
770
+ */
771
+ protected stagesRepairs(document: LangiumDocument): boolean {
772
+ return this.syncMode === 'editor' && !this.textDocuments.isOpen(document.textDocument.uri);
773
+ }
774
+
775
+ /**
776
+ * What tells `repair` made again from the same text apart from a new one,
777
+ * for a document that {@link stagesRepairs}, which reads the unrepaired
778
+ * text again in every build that re-parses it after its CST was shed. Such
779
+ * a repair requests no build, because the build that re-parsed the
780
+ * document relinked its dependents, and another would replay it again.
781
+ * Hashes, so the record keeps no text the shed freed.
782
+ */
783
+ protected replayKey(document: LangiumDocument, repair: IntegrityRepairTexts): string | undefined {
784
+ return this.stagesRepairs(document) ? textHash(repair.from) + textHash(repair.to) : undefined;
785
+ }
786
+
787
+ /**
788
+ * Drop the {@link replayKey} of `document` once it was parsed from other
789
+ * text than its repair came from, such as a save and a revert. A repair of
790
+ * that text again is a new one, whose dependents need their build.
791
+ */
792
+ protected forgetReplayOfOtherText(document: LangiumDocument): void {
793
+ const recorded = this.replayKeys.get(document);
794
+ const parsedFrom = document.parseResult.value.$cstNode?.root.fullText;
795
+ if (recorded !== undefined && parsedFrom !== undefined && recorded.fromHash !== textHash(parsedFrom)) {
796
+ this.replayKeys.delete(document);
376
797
  }
377
798
  }
378
799
 
@@ -381,37 +802,152 @@ export class DefaultIntegrityService<TRoot extends AstNode = AstNode> implements
381
802
  * already in the synced store (in-place in {@link resyncDocument}); this
382
803
  * method routes the out-of-band part by document registration:
383
804
  *
384
- * - **Open in the language client** → nothing to push here. The AST mutation
385
- * rode the current build, so `ModelService.syncToLanguageClient` (the
386
- * persistent integrity-settled listener) mirrors the corrected text to the
387
- * client by *content* (shadow diff) — no authorship rewrite, no second
388
- * rebuild.
805
+ * - **Open in the language client** → nothing to push here. The AST
806
+ * mutation rode the current build, so
807
+ * `DefaultModelService.syncToLanguageClient` (the persistent
808
+ * integrity-settled listener) mirrors it to the client by
809
+ * *content* (shadow diff), and the editor shows it as an unsaved change.
810
+ * - **Held only through the data or GLSP head**, or waiting out the release
811
+ * grace after its last client's connection was lost → the store is the
812
+ * authority, and those heads read the correction from the settled build.
813
+ * In silent mode it is also written to disk when the text it was computed
814
+ * from is what disk holds, since the repair is then the only difference;
815
+ * otherwise it waits for the next save. Editor mode always waits, and the
816
+ * repair leaves the document dirty until a save.
389
817
  * - **Closed, silent mode** → write to disk directly (disk is authoritative).
390
- * - **Closed, editor mode** → stage so the next open picks it up via applyEdit.
818
+ * - **Closed, editor mode** → stage, so the next open takes the correction
819
+ * over the disk text.
820
+ *
821
+ * A held URI is not closed. Writing it as one persists the holder's unsaved
822
+ * edits with the repair, and staging it leaves text no open reads, because
823
+ * only a first open consumes the stage.
824
+ *
825
+ * `parsedFrom` is the text the repaired AST was parsed from. Without it a
826
+ * held URI's repair is left to the next save.
391
827
  */
392
- protected async syncCorrections(document: TextDocument): Promise<void> {
393
- // Open files: delivery is the ModelService settled listener's job (by
394
- // content, via the shadow) — nothing to do here. Only the open/closed
395
- // question is asked, so no reverse address resolution is needed: a file
396
- // open under a symlinked path still answers `true`.
828
+ protected async syncCorrections(document: TextDocument, parsedFrom?: string): Promise<void> {
829
+ // Only the open/closed question is asked, so no reverse address
830
+ // resolution is needed: a file open under a symlinked path still answers
831
+ // `true`.
397
832
  if (this.textDocuments.isOpenInLanguageClient(document.uri)) {
398
833
  return;
399
834
  }
400
835
 
836
+ // A virtual document has no disk backing, so neither persisting nor
837
+ // staging applies: there is no file to write and no editor that could open
838
+ // one. `URI.fsPath` yields a path for any scheme, so handing this to the
839
+ // provider would address `<cwd>/<contributor>/<name>` — an unrelated
840
+ // location a disk-backed provider refuses outright, turning a repair that
841
+ // was never persistable into a build failure. The correction is already on
842
+ // the AST and in the text store, which is the whole of where a virtual
843
+ // document lives, so there is nothing further to propagate.
844
+ if (isVirtualUri(document.uri)) {
845
+ this.tracer.with(document.uri).debug('Virtual document: correction kept in memory, nothing to persist');
846
+ return;
847
+ }
848
+
849
+ // A document waiting out the release grace still holds its lost client's
850
+ // unsaved text, so writing it as a closed file would persist that text.
851
+ if (this.textDocuments.isOpenInAnyClient(document.uri) || this.textDocuments.isReleaseDeferred(document.uri)) {
852
+ if (this.syncMode === 'silent' && parsedFrom !== undefined) {
853
+ await this.persistIfDiskMatches(document, parsedFrom);
854
+ }
855
+ return;
856
+ }
857
+
401
858
  // Closed files, silent mode: write to disk directly.
402
859
  if (this.syncMode === 'silent') {
860
+ const repaired = document.getText();
403
861
  // Async write so the integrity pass does not block the event loop on a slow filesystem.
404
- await this.fileSystemProvider.writeFile(UriUtils.toUri(document.uri), document.getText());
862
+ // In the file's task queue, behind any save already queued: written
863
+ // beside one, the older repair can land last and stay on disk.
864
+ await this.services.shared.workspace.FileSystemTaskQueue.enqueue(document.uri, () =>
865
+ this.fileSystemProvider.writeFile(UriUtils.toUri(document.uri), repaired)
866
+ );
867
+ this.noteSilentWrite(document.uri);
405
868
  return;
406
869
  }
407
870
 
408
- // Closed files, editor mode: stage content so the next open picks it up via applyEdit.
871
+ // Closed files, editor mode: stage content for the next open.
409
872
  this.textDocuments.stagePendingContent(document.uri, document.getText());
410
873
  }
411
874
 
875
+ /**
876
+ * Write the repair in `document` to disk when disk still holds `parsedFrom`,
877
+ * the text the repair was computed from — so the write carries the repair
878
+ * and nothing a holder could still discard.
879
+ *
880
+ * The read, the compare and the write run as one task in the file's task
881
+ * queue, so no save of the file lands between them. The store is compared
882
+ * again once the read returns, and the write is skipped if it no longer
883
+ * holds the repair: an edit does not wait for the queue, and writing the
884
+ * older repair then puts text on disk that no participant holds. The
885
+ * skipped repair loses nothing: the edit drives a build of its own, and a
886
+ * save writes the store's text. A file that cannot be read is left alone
887
+ * too: there is no evidence it matches. So is a file an editor opened in the
888
+ * meantime: the editor receives the repair as an unsaved change, and writing
889
+ * it would change the file under the editor.
890
+ */
891
+ protected async persistIfDiskMatches(document: TextDocument, parsedFrom: string): Promise<void> {
892
+ const uri = UriUtils.toUri(document.uri);
893
+ const repaired = document.getText();
894
+ await this.services.shared.workspace.FileSystemTaskQueue.enqueue(document.uri, async () => {
895
+ let onDisk: string;
896
+ try {
897
+ onDisk = await this.fileSystemProvider.readFile(uri);
898
+ } catch (error: unknown) {
899
+ this.tracer.with(document.uri).debug(`Held document unreadable on disk, repair left to the next save: ${String(error)}`);
900
+ return;
901
+ }
902
+ // The file itself, not the store's disk baseline: the baseline moves
903
+ // only when the server hears of a change, so it can still hold the
904
+ // parsed text after another process rewrote the file and before the
905
+ // watcher reports it, and this write would then replace that change.
906
+ if (onDisk !== parsedFrom) {
907
+ return;
908
+ }
909
+ if (this.textDocuments.get(document.uri)?.getText() !== repaired) {
910
+ this.tracer.with(document.uri).debug('Held document changed while disk was read, repair left to that change');
911
+ return;
912
+ }
913
+ if (this.textDocuments.isOpenInLanguageClient(document.uri)) {
914
+ this.tracer.with(document.uri).debug('Editor attached while disk was read, repair left to its save');
915
+ return;
916
+ }
917
+ await this.fileSystemProvider.writeFile(uri, repaired);
918
+ this.textDocuments.setDiskBaseline(document.uri, repaired);
919
+ this.noteSilentWrite(document.uri);
920
+ });
921
+ }
922
+
923
+ /**
924
+ * Hand the first repair written to disk in `'silent'` sync mode to
925
+ * {@link reportSilentWrite}. Once per server, whichever language writes,
926
+ * rather than per file: a workspace-wide repair pass would otherwise log a
927
+ * line per document.
928
+ */
929
+ protected noteSilentWrite(uri: string): void {
930
+ if (silentWriteReported.has(this.services.shared)) {
931
+ return;
932
+ }
933
+ silentWriteReported.add(this.services.shared);
934
+ this.reportSilentWrite(uri);
935
+ }
936
+
937
+ /**
938
+ * Log a server's first silent write-back, so an adopter learns that
939
+ * integrity rules edit their files and which setting controls it.
940
+ */
941
+ protected reportSilentWrite(uri: string): void {
942
+ this.tracer.info(
943
+ `Wrote an integrity repair to ${uri}. The integrity service's syncMode option is 'silent', ` +
944
+ "which writes repairs to disk; 'editor' leaves disk alone. Later repairs are written without this line."
945
+ );
946
+ }
947
+
412
948
  /**
413
949
  * Format a rule's name for inclusion in user-facing diagnostic strings
414
- * (error messages thrown during registration). Defaults to
950
+ * (error messages thrown during registration, and log lines). Defaults to
415
951
  * `label ?? id`. Adopters override when their error reporting needs
416
952
  * richer rule context.
417
953
  */
@@ -419,6 +955,15 @@ export class DefaultIntegrityService<TRoot extends AstNode = AstNode> implements
419
955
  return rule.label ?? rule.id;
420
956
  }
421
957
 
958
+ /** `rules` as the subject of a log line, or a rule left unnamed when an override recorded none. */
959
+ protected formatRules(rules: readonly IntegrityRule[]): string {
960
+ if (rules.length === 0) {
961
+ return 'An integrity rule';
962
+ }
963
+ const names = rules.map(rule => `'${this.formatRule(rule)}'`).join(', ');
964
+ return rules.length === 1 ? `Integrity rule ${names}` : `Integrity rules ${names}`;
965
+ }
966
+
422
967
  /**
423
968
  * Format an AST node for the per-mutation `trace` log line. Returns
424
969
  * `'TypeName'` or `"TypeName('label')"` when the node carries a string
@@ -434,17 +979,37 @@ export class DefaultIntegrityService<TRoot extends AstNode = AstNode> implements
434
979
  return label ? `${node.$type}('${label}')` : node.$type;
435
980
  }
436
981
 
437
- protected getPhaseBucket(phase: DocumentState): readonly IntegrityRule[] {
982
+ /**
983
+ * The `$type`s `rule` runs on. Defaults to its `nodeType` and every subtype,
984
+ * as Langium's validation registry does: no node carries an abstract or union
985
+ * type as its `$type`, so an exact match never runs a rule keyed on one.
986
+ * Read only when the phase buckets rebuild after a registration change, so an
987
+ * override must not depend on anything else.
988
+ */
989
+ protected nodeTypesFor(rule: IntegrityRule): Iterable<string> {
990
+ // A type the reflection does not know still matches itself.
991
+ return [rule.nodeType, ...this.services.shared.AstReflection.getAllSubTypes(rule.nodeType)];
992
+ }
993
+
994
+ protected getPhaseBucket(phase: DocumentState): ReadonlyMap<string, readonly IntegrityRule[]> {
438
995
  const current = this.rules.all();
439
996
  if (this.phaseBucketsFor !== current) {
440
- const next = new Map<DocumentState, IntegrityRule[]>();
997
+ const next = new Map<DocumentState, Map<string, IntegrityRule[]>>();
441
998
  for (const rule of current) {
442
999
  let bucket = next.get(rule.phase);
443
1000
  if (!bucket) {
444
- bucket = [];
1001
+ bucket = new Map();
445
1002
  next.set(rule.phase, bucket);
446
1003
  }
447
- bucket.push(rule);
1004
+ // Deduplicated, or a rule runs twice on a node of its own type.
1005
+ for (const nodeType of new Set(this.nodeTypesFor(rule))) {
1006
+ let rules = bucket.get(nodeType);
1007
+ if (!rules) {
1008
+ rules = [];
1009
+ bucket.set(nodeType, rules);
1010
+ }
1011
+ rules.push(rule);
1012
+ }
448
1013
  }
449
1014
  this.phaseBuckets = next;
450
1015
  this.phaseBucketsFor = current;
@@ -453,5 +1018,6 @@ export class DefaultIntegrityService<TRoot extends AstNode = AstNode> implements
453
1018
  }
454
1019
  }
455
1020
 
456
- /** Shared empty result so `getPhaseBucket` doesn't allocate per call on the cold path. */
457
- const EMPTY_PHASE_BUCKET: readonly IntegrityRule[] = Object.freeze([]);
1021
+ // Shared empty results so neither the cold path nor a node no rule matches allocates.
1022
+ const EMPTY_PHASE_BUCKET: ReadonlyMap<string, readonly IntegrityRule[]> = new Map();
1023
+ const NO_RULES: readonly IntegrityRule[] = Object.freeze([]);