@domainlang/language 0.1.20

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 (212) hide show
  1. package/README.md +163 -0
  2. package/out/ast-augmentation.d.ts +6 -0
  3. package/out/ast-augmentation.js +2 -0
  4. package/out/ast-augmentation.js.map +1 -0
  5. package/out/domain-lang-module.d.ts +57 -0
  6. package/out/domain-lang-module.js +67 -0
  7. package/out/domain-lang-module.js.map +1 -0
  8. package/out/generated/ast.d.ts +759 -0
  9. package/out/generated/ast.js +556 -0
  10. package/out/generated/ast.js.map +1 -0
  11. package/out/generated/grammar.d.ts +6 -0
  12. package/out/generated/grammar.js +2407 -0
  13. package/out/generated/grammar.js.map +1 -0
  14. package/out/generated/module.d.ts +13 -0
  15. package/out/generated/module.js +21 -0
  16. package/out/generated/module.js.map +1 -0
  17. package/out/index.d.ts +16 -0
  18. package/out/index.js +22 -0
  19. package/out/index.js.map +1 -0
  20. package/out/lsp/domain-lang-code-actions.d.ts +55 -0
  21. package/out/lsp/domain-lang-code-actions.js +143 -0
  22. package/out/lsp/domain-lang-code-actions.js.map +1 -0
  23. package/out/lsp/domain-lang-completion.d.ts +37 -0
  24. package/out/lsp/domain-lang-completion.js +452 -0
  25. package/out/lsp/domain-lang-completion.js.map +1 -0
  26. package/out/lsp/domain-lang-formatter.d.ts +15 -0
  27. package/out/lsp/domain-lang-formatter.js +43 -0
  28. package/out/lsp/domain-lang-formatter.js.map +1 -0
  29. package/out/lsp/domain-lang-naming.d.ts +34 -0
  30. package/out/lsp/domain-lang-naming.js +49 -0
  31. package/out/lsp/domain-lang-naming.js.map +1 -0
  32. package/out/lsp/domain-lang-scope.d.ts +59 -0
  33. package/out/lsp/domain-lang-scope.js +102 -0
  34. package/out/lsp/domain-lang-scope.js.map +1 -0
  35. package/out/lsp/domain-lang-workspace-manager.d.ts +21 -0
  36. package/out/lsp/domain-lang-workspace-manager.js +93 -0
  37. package/out/lsp/domain-lang-workspace-manager.js.map +1 -0
  38. package/out/lsp/hover/ddd-pattern-explanations.d.ts +50 -0
  39. package/out/lsp/hover/ddd-pattern-explanations.js +196 -0
  40. package/out/lsp/hover/ddd-pattern-explanations.js.map +1 -0
  41. package/out/lsp/hover/domain-lang-hover.d.ts +19 -0
  42. package/out/lsp/hover/domain-lang-hover.js +302 -0
  43. package/out/lsp/hover/domain-lang-hover.js.map +1 -0
  44. package/out/lsp/hover/domain-lang-keywords.d.ts +13 -0
  45. package/out/lsp/hover/domain-lang-keywords.js +47 -0
  46. package/out/lsp/hover/domain-lang-keywords.js.map +1 -0
  47. package/out/lsp/manifest-diagnostics.d.ts +82 -0
  48. package/out/lsp/manifest-diagnostics.js +230 -0
  49. package/out/lsp/manifest-diagnostics.js.map +1 -0
  50. package/out/main-browser.d.ts +1 -0
  51. package/out/main-browser.js +11 -0
  52. package/out/main-browser.js.map +1 -0
  53. package/out/main.d.ts +1 -0
  54. package/out/main.js +74 -0
  55. package/out/main.js.map +1 -0
  56. package/out/sdk/ast-augmentation.d.ts +136 -0
  57. package/out/sdk/ast-augmentation.js +62 -0
  58. package/out/sdk/ast-augmentation.js.map +1 -0
  59. package/out/sdk/index.d.ts +94 -0
  60. package/out/sdk/index.js +97 -0
  61. package/out/sdk/index.js.map +1 -0
  62. package/out/sdk/indexes.d.ts +16 -0
  63. package/out/sdk/indexes.js +97 -0
  64. package/out/sdk/indexes.js.map +1 -0
  65. package/out/sdk/loader-node.d.ts +51 -0
  66. package/out/sdk/loader-node.js +119 -0
  67. package/out/sdk/loader-node.js.map +1 -0
  68. package/out/sdk/loader.d.ts +49 -0
  69. package/out/sdk/loader.js +85 -0
  70. package/out/sdk/loader.js.map +1 -0
  71. package/out/sdk/patterns.d.ts +93 -0
  72. package/out/sdk/patterns.js +123 -0
  73. package/out/sdk/patterns.js.map +1 -0
  74. package/out/sdk/query.d.ts +90 -0
  75. package/out/sdk/query.js +679 -0
  76. package/out/sdk/query.js.map +1 -0
  77. package/out/sdk/resolution.d.ts +52 -0
  78. package/out/sdk/resolution.js +68 -0
  79. package/out/sdk/resolution.js.map +1 -0
  80. package/out/sdk/types.d.ts +280 -0
  81. package/out/sdk/types.js +8 -0
  82. package/out/sdk/types.js.map +1 -0
  83. package/out/services/dependency-analyzer.d.ts +58 -0
  84. package/out/services/dependency-analyzer.js +254 -0
  85. package/out/services/dependency-analyzer.js.map +1 -0
  86. package/out/services/dependency-resolver.d.ts +146 -0
  87. package/out/services/dependency-resolver.js +452 -0
  88. package/out/services/dependency-resolver.js.map +1 -0
  89. package/out/services/git-url-resolver.browser.d.ts +10 -0
  90. package/out/services/git-url-resolver.browser.js +19 -0
  91. package/out/services/git-url-resolver.browser.js.map +1 -0
  92. package/out/services/git-url-resolver.d.ts +158 -0
  93. package/out/services/git-url-resolver.js +416 -0
  94. package/out/services/git-url-resolver.js.map +1 -0
  95. package/out/services/governance-validator.d.ts +44 -0
  96. package/out/services/governance-validator.js +153 -0
  97. package/out/services/governance-validator.js.map +1 -0
  98. package/out/services/import-resolver.d.ts +77 -0
  99. package/out/services/import-resolver.js +240 -0
  100. package/out/services/import-resolver.js.map +1 -0
  101. package/out/services/performance-optimizer.d.ts +60 -0
  102. package/out/services/performance-optimizer.js +140 -0
  103. package/out/services/performance-optimizer.js.map +1 -0
  104. package/out/services/relationship-inference.d.ts +11 -0
  105. package/out/services/relationship-inference.js +98 -0
  106. package/out/services/relationship-inference.js.map +1 -0
  107. package/out/services/semver.d.ts +98 -0
  108. package/out/services/semver.js +195 -0
  109. package/out/services/semver.js.map +1 -0
  110. package/out/services/types.d.ts +340 -0
  111. package/out/services/types.js +46 -0
  112. package/out/services/types.js.map +1 -0
  113. package/out/services/workspace-manager.d.ts +123 -0
  114. package/out/services/workspace-manager.js +489 -0
  115. package/out/services/workspace-manager.js.map +1 -0
  116. package/out/syntaxes/domain-lang.monarch.d.ts +76 -0
  117. package/out/syntaxes/domain-lang.monarch.js +29 -0
  118. package/out/syntaxes/domain-lang.monarch.js.map +1 -0
  119. package/out/utils/import-utils.d.ts +49 -0
  120. package/out/utils/import-utils.js +128 -0
  121. package/out/utils/import-utils.js.map +1 -0
  122. package/out/validation/bounded-context.d.ts +11 -0
  123. package/out/validation/bounded-context.js +79 -0
  124. package/out/validation/bounded-context.js.map +1 -0
  125. package/out/validation/classification.d.ts +3 -0
  126. package/out/validation/classification.js +3 -0
  127. package/out/validation/classification.js.map +1 -0
  128. package/out/validation/constants.d.ts +180 -0
  129. package/out/validation/constants.js +235 -0
  130. package/out/validation/constants.js.map +1 -0
  131. package/out/validation/domain-lang-validator.d.ts +2 -0
  132. package/out/validation/domain-lang-validator.js +27 -0
  133. package/out/validation/domain-lang-validator.js.map +1 -0
  134. package/out/validation/domain.d.ts +11 -0
  135. package/out/validation/domain.js +63 -0
  136. package/out/validation/domain.js.map +1 -0
  137. package/out/validation/import.d.ts +68 -0
  138. package/out/validation/import.js +237 -0
  139. package/out/validation/import.js.map +1 -0
  140. package/out/validation/manifest.d.ts +144 -0
  141. package/out/validation/manifest.js +327 -0
  142. package/out/validation/manifest.js.map +1 -0
  143. package/out/validation/maps.d.ts +21 -0
  144. package/out/validation/maps.js +60 -0
  145. package/out/validation/maps.js.map +1 -0
  146. package/out/validation/metadata.d.ts +7 -0
  147. package/out/validation/metadata.js +16 -0
  148. package/out/validation/metadata.js.map +1 -0
  149. package/out/validation/model.d.ts +12 -0
  150. package/out/validation/model.js +29 -0
  151. package/out/validation/model.js.map +1 -0
  152. package/out/validation/relationships.d.ts +12 -0
  153. package/out/validation/relationships.js +94 -0
  154. package/out/validation/relationships.js.map +1 -0
  155. package/out/validation/shared.d.ts +6 -0
  156. package/out/validation/shared.js +12 -0
  157. package/out/validation/shared.js.map +1 -0
  158. package/package.json +110 -0
  159. package/src/ast-augmentation.ts +5 -0
  160. package/src/domain-lang-module.ts +112 -0
  161. package/src/domain-lang.langium +351 -0
  162. package/src/generated/ast.ts +986 -0
  163. package/src/generated/grammar.ts +2409 -0
  164. package/src/generated/module.ts +25 -0
  165. package/src/index.ts +24 -0
  166. package/src/lsp/domain-lang-code-actions.ts +189 -0
  167. package/src/lsp/domain-lang-completion.ts +514 -0
  168. package/src/lsp/domain-lang-formatter.ts +51 -0
  169. package/src/lsp/domain-lang-naming.ts +56 -0
  170. package/src/lsp/domain-lang-scope.ts +137 -0
  171. package/src/lsp/domain-lang-workspace-manager.ts +104 -0
  172. package/src/lsp/hover/ddd-pattern-explanations.ts +237 -0
  173. package/src/lsp/hover/domain-lang-hover.ts +338 -0
  174. package/src/lsp/hover/domain-lang-keywords.ts +50 -0
  175. package/src/lsp/manifest-diagnostics.ts +290 -0
  176. package/src/main-browser.ts +15 -0
  177. package/src/main.ts +85 -0
  178. package/src/sdk/README.md +297 -0
  179. package/src/sdk/ast-augmentation.ts +157 -0
  180. package/src/sdk/index.ts +126 -0
  181. package/src/sdk/indexes.ts +155 -0
  182. package/src/sdk/loader-node.ts +146 -0
  183. package/src/sdk/loader.ts +99 -0
  184. package/src/sdk/patterns.ts +147 -0
  185. package/src/sdk/query.ts +802 -0
  186. package/src/sdk/resolution.ts +78 -0
  187. package/src/sdk/types.ts +323 -0
  188. package/src/services/dependency-analyzer.ts +321 -0
  189. package/src/services/dependency-resolver.ts +551 -0
  190. package/src/services/git-url-resolver.browser.ts +26 -0
  191. package/src/services/git-url-resolver.ts +517 -0
  192. package/src/services/governance-validator.ts +177 -0
  193. package/src/services/import-resolver.ts +292 -0
  194. package/src/services/performance-optimizer.ts +170 -0
  195. package/src/services/relationship-inference.ts +121 -0
  196. package/src/services/semver.ts +213 -0
  197. package/src/services/types.ts +415 -0
  198. package/src/services/workspace-manager.ts +607 -0
  199. package/src/syntaxes/domain-lang.monarch.ts +29 -0
  200. package/src/utils/import-utils.ts +152 -0
  201. package/src/validation/bounded-context.ts +99 -0
  202. package/src/validation/classification.ts +5 -0
  203. package/src/validation/constants.ts +304 -0
  204. package/src/validation/domain-lang-validator.ts +33 -0
  205. package/src/validation/domain.ts +77 -0
  206. package/src/validation/import.ts +295 -0
  207. package/src/validation/manifest.ts +439 -0
  208. package/src/validation/maps.ts +76 -0
  209. package/src/validation/metadata.ts +18 -0
  210. package/src/validation/model.ts +37 -0
  211. package/src/validation/relationships.ts +154 -0
  212. package/src/validation/shared.ts +14 -0
@@ -0,0 +1,338 @@
1
+ import type {
2
+ AstNode,
3
+ CommentProvider,
4
+ DocumentationProvider,
5
+ LangiumDocument,
6
+ MaybePromise,
7
+ Reference
8
+ } from 'langium';
9
+ import { CstUtils, isAstNodeWithComment, isJSDoc, isReference, parseJSDoc } from 'langium';
10
+ import type { LangiumServices } from 'langium/lsp';
11
+ import { AstNodeHoverProvider } from 'langium/lsp';
12
+ import type { Hover, HoverParams } from 'vscode-languageserver';
13
+ import * as ast from '../../generated/ast.js';
14
+ import type { DomainLangServices } from '../../domain-lang-module.js';
15
+ import { QualifiedNameProvider } from '../domain-lang-naming.js';
16
+ import { keywordExplanations } from './domain-lang-keywords.js';
17
+ import { effectiveClassification, effectiveTeam } from '../../sdk/resolution.js';
18
+
19
+ /**
20
+ * Provides hover information for DomainLang elements.
21
+ */
22
+ export class DomainLangHoverProvider extends AstNodeHoverProvider {
23
+ protected readonly documentationProvider: DocumentationProvider;
24
+ protected readonly commentProvider: CommentProvider;
25
+ protected readonly qualifiedNameProvider: QualifiedNameProvider;
26
+
27
+ constructor(services: LangiumServices) {
28
+ super(services);
29
+ this.documentationProvider = services.documentation.DocumentationProvider;
30
+ this.commentProvider = services.documentation.CommentProvider;
31
+ const domainServices = services as DomainLangServices;
32
+ this.qualifiedNameProvider = domainServices.references.QualifiedNameProvider;
33
+ }
34
+
35
+ override async getHoverContent(document: LangiumDocument, params: HoverParams): Promise<Hover | undefined> {
36
+ try {
37
+ const rootNode = document.parseResult?.value?.$cstNode;
38
+ if (!rootNode) {
39
+ return undefined;
40
+ }
41
+
42
+ const offset = document.textDocument.offsetAt(params.position);
43
+ const cstNode = CstUtils.findDeclarationNodeAtOffset(rootNode, offset, this.grammarConfig.nameRegexp);
44
+ if (!cstNode || cstNode.offset + cstNode.length <= offset) {
45
+ return undefined;
46
+ }
47
+
48
+ const targetNodes = this.references.findDeclarations(cstNode);
49
+ const targetNode = targetNodes?.[0];
50
+ if (targetNode) {
51
+ const content = await this.getAstNodeHoverContent(targetNode);
52
+ if (content) {
53
+ return { contents: { kind: 'markdown', value: content } };
54
+ }
55
+ }
56
+
57
+ if (cstNode.astNode && ast.isThisRef(cstNode.astNode)) {
58
+ const content = await this.getAstNodeHoverContent(cstNode.astNode);
59
+ if (content) {
60
+ return { contents: { kind: 'markdown', value: content } };
61
+ }
62
+ }
63
+
64
+ if (cstNode.grammarSource?.$type === 'Keyword') {
65
+ const keywordHover = this.getKeywordHoverContent(cstNode.grammarSource);
66
+ if (keywordHover) {
67
+ return keywordHover;
68
+ }
69
+
70
+ const explanation = keywordExplanations[cstNode.text.toLowerCase()];
71
+ if (explanation) {
72
+ return { contents: { kind: 'markdown', value: `💡 ${explanation}` } };
73
+ }
74
+ }
75
+ } catch (error) {
76
+ console.error('Error in getHoverContent:', error);
77
+ }
78
+
79
+ return undefined;
80
+ }
81
+
82
+ protected getAstNodeHoverContent(node: AstNode): MaybePromise<string | undefined> {
83
+ try {
84
+ const content = this.documentationProvider.getDocumentation(node);
85
+ const commentBlock = content ? `*${content}*\n\n` : '';
86
+
87
+ if (ast.isDomain(node)) {
88
+ const description = node.description ?? '';
89
+ const vision = node.vision ?? '';
90
+ const typeRef = node.type?.ref;
91
+ const type = this.getRefName(typeRef);
92
+
93
+ const signatureParts = ['Domain', node.name];
94
+ if (node.parent?.ref?.name) signatureParts.push('in', node.parent.ref.name);
95
+ const signature = `\`\`\`domain-lang\n${signatureParts.join(' ')}\n\`\`\``;
96
+
97
+ const fields: string[] = [signature];
98
+ if (description) fields.push(description);
99
+ if (vision || type || node.parent) fields.push('---');
100
+ if (vision) fields.push(`**Vision:** ${vision}`);
101
+ if (type) fields.push(`**Type:** ${this.refLink(typeRef, type)}`);
102
+ if (node.parent) fields.push(`**Parent:** ${this.refLink(node.parent)}`);
103
+
104
+ return (commentBlock ? `${commentBlock}\n\n---\n\n` : '') +
105
+ `📁 **\`(domain)\` ${node.name}**\n\n` +
106
+ fields.join('\n\n');
107
+ }
108
+
109
+ if (ast.isThisRef(node)) {
110
+ let parent = node.$container;
111
+ while (parent) {
112
+ if (
113
+ ast.isDomain(parent) ||
114
+ ast.isBoundedContext(parent) ||
115
+ ast.isNamespaceDeclaration(parent) ||
116
+ ast.isContextMap(parent) ||
117
+ ast.isDomainMap(parent) ||
118
+ ast.isModel(parent)
119
+ ) {
120
+ return this.getAstNodeHoverContent(parent);
121
+ }
122
+ parent = parent.$container;
123
+ }
124
+
125
+ return '*this* refers to the current context';
126
+ }
127
+
128
+ if (ast.isBoundedContext(node)) {
129
+ const description = node.description ?? '';
130
+ const classification = effectiveClassification(node);
131
+ const team = effectiveTeam(node);
132
+ const businessModel = node.businessModel?.ref;
133
+ const evolution = node.evolution?.ref;
134
+ const relationships = node.relationships ?? [];
135
+ const terminology = node.terminology ?? [];
136
+ const decisions = node.decisions ?? [];
137
+ const classificationName = classification?.name;
138
+ const teamName = team?.name;
139
+
140
+ const signatureParts = ['boundedcontext', node.name];
141
+ if (node.domain?.ref?.name) signatureParts.push('for', node.domain.ref.name);
142
+ if (classificationName) signatureParts.push('as', classificationName);
143
+ if (teamName) signatureParts.push('by', teamName);
144
+ const signature = `\`\`\`domain-lang\n${signatureParts.join(' ')}\n\`\`\``;
145
+
146
+ const fields: string[] = [signature];
147
+ if (description) fields.push(description);
148
+ if (classification || team || businessModel || evolution) fields.push('---');
149
+ if (classification) fields.push(`🔖 **Classification:** ${this.refLink(classification)}`);
150
+ if (team) fields.push(`👥 **Team:** ${this.refLink(team)}`);
151
+ if (businessModel) fields.push(`💼 **Business Model:** ${this.refLink(businessModel)}`);
152
+ if (evolution) fields.push(`🔄 **Evolution:** ${this.refLink(evolution)}`);
153
+ if (relationships.length) {
154
+ const relationshipLines = relationships.map(rel => `- ${this.refLink(rel.left?.link)} ${rel.arrow} ${this.refLink(rel.right?.link)}${rel.type ? ` \`${rel.type}\`` : ''}`);
155
+ fields.push(`**Relationships:**\n${relationshipLines.join('\n')}`);
156
+ }
157
+ if (terminology.length) {
158
+ const termLines = terminology.map(t => `- \`${t.name}\`: ${t.meaning ?? ''}`);
159
+ fields.push(`**Terminology:**\n${termLines.join('\n')}`);
160
+ }
161
+ if (decisions.length) {
162
+ const decisionLines = decisions.map(d => `- \`${d.name}\`: ${d.value ?? ''}`);
163
+ fields.push(`**Decisions:**\n${decisionLines.join('\n')}`);
164
+ }
165
+
166
+ return (commentBlock ? `${commentBlock}\n\n---\n\n` : '') +
167
+ `📕 **\`(boundedcontext)\` ${node.name}**\n\n` +
168
+ fields.join('\n\n');
169
+ }
170
+
171
+ if (ast.isNamespaceDeclaration && ast.isNamespaceDeclaration(node)) {
172
+ const fields: string[] = [`Contains ${node.children.length} elements.`];
173
+ return (commentBlock ? `${commentBlock}\n\n---\n\n` : '') +
174
+ `🧭 **\`(namespace)\` ${node.name}**\n\n` +
175
+ fields.join('\n\n');
176
+ }
177
+
178
+ if (ast.isContextMap && ast.isContextMap(node)) {
179
+ const fields: string[] = [];
180
+ if (node.boundedContexts.length) {
181
+ fields.push('---');
182
+ fields.push(`**📕 Bounded Contexts**\n${node.boundedContexts.flatMap(bc => bc.items.map(item => `- ${this.refLink(item.ref)}`)).join('\n')}`);
183
+ }
184
+ if (node.relationships.length) {
185
+ fields.push('---');
186
+ fields.push(`**🔗 Relationships**\n${node.relationships.map(r => `- ${this.refLink(r.left?.link)} ${r.arrow} ${this.refLink(r.right?.link)}${r.type ? ` \`${r.type}\`` : ''}`).join('\n')}`);
187
+ }
188
+ return (commentBlock ? `${commentBlock}\n\n---\n\n` : '') +
189
+ `🗺️ **\`(contextmap)\` ${node.name}**\n\n` +
190
+ fields.join('\n\n');
191
+ }
192
+
193
+ if (ast.isDomainMap && ast.isDomainMap(node)) {
194
+ const fields: string[] = [];
195
+ if (node.domains.length) {
196
+ fields.push('---');
197
+ fields.push(`**📁 Domains**\n${node.domains.flatMap(d => d.items.map(item => `- ${this.refLink(item.ref)}`)).join('\n')}`);
198
+ }
199
+ return (commentBlock ? `${commentBlock}\n\n---\n\n` : '') +
200
+ `🗺️ **\`(domainmap)\` ${node.name}**\n\n` +
201
+ fields.join('\n\n');
202
+ }
203
+
204
+ if (ast.isDecision && ast.isDecision(node)) {
205
+ const fields: string[] = [];
206
+ if (node.value) {
207
+ fields.push('---');
208
+ fields.push(`*Definition:* ${node.value}`);
209
+ }
210
+ return (commentBlock ? `${commentBlock}\n\n---\n\n` : '') +
211
+ `⚖️ **\`(decision)\` ${node.name}**\n\n` +
212
+ fields.join('\n\n');
213
+ }
214
+
215
+ if (ast.isPolicy && ast.isPolicy(node)) {
216
+ const fields: string[] = [];
217
+ if (node.value) {
218
+ fields.push('---');
219
+ fields.push(`*Definition:* ${node.value}`);
220
+ }
221
+ return (commentBlock ? `${commentBlock}\n\n---\n\n` : '') +
222
+ `📜 **\`(policy)\` ${node.name}**\n\n` +
223
+ fields.join('\n\n');
224
+ }
225
+
226
+ if (ast.isBusinessRule && ast.isBusinessRule(node)) {
227
+ const fields: string[] = [];
228
+ if (node.value) {
229
+ fields.push('---');
230
+ fields.push(`*Definition:* ${node.value}`);
231
+ }
232
+ return (commentBlock ? `${commentBlock}\n\n---\n\n` : '') +
233
+ `⚖️ **\`(rule)\` ${node.name}**\n\n` +
234
+ fields.join('\n\n');
235
+ }
236
+
237
+ if (ast.isDomainTerm && ast.isDomainTerm(node)) {
238
+ const fields: string[] = [];
239
+ if (node.meaning) {
240
+ fields.push('---');
241
+ fields.push(`*${node.meaning}*`);
242
+ }
243
+ return (commentBlock ? `${commentBlock}\n\n---\n\n` : '') +
244
+ `🗝️ **\`(term)\` ${node.name}**\n\n` +
245
+ fields.join('\n\n');
246
+ }
247
+
248
+ if (ast.isTeam && ast.isTeam(node)) {
249
+ return (commentBlock ? `${commentBlock}\n\n---\n\n` : '') +
250
+ `👥 **\`(team)\` ${node.name}**`;
251
+ }
252
+
253
+ if (ast.isClassification && ast.isClassification(node)) {
254
+ return (commentBlock ? `${commentBlock}\n\n---\n\n` : '') +
255
+ `🏷️ **\`(classification)\` ${node.name}**`;
256
+ }
257
+
258
+ if (ast.isMetadata && ast.isMetadata(node)) {
259
+ return (commentBlock ? `${commentBlock}\n\n---\n\n` : '') +
260
+ `🔖 **\`(metadata)\` ${node.name}**`;
261
+ }
262
+
263
+ if (ast.isRelationship && ast.isRelationship(node)) {
264
+ const leftPatterns = node.leftPatterns.join(', ');
265
+ const rightPatterns = node.rightPatterns.join(', ');
266
+ const fields: string[] = [];
267
+ fields.push(`${this.refLink(node.left.link)} ${node.arrow} ${this.refLink(node.right.link)}`);
268
+ if (node.type) fields.push(`**Type:** \`${node.type}\``);
269
+ if (leftPatterns) fields.push(`**Left patterns:** ${leftPatterns}`);
270
+ if (rightPatterns) fields.push(`**Right patterns:** ${rightPatterns}`);
271
+ return (commentBlock ? `${commentBlock}\n\n---\n\n` : '') +
272
+ `🔗 **\`(relationship)\`**\n\n` +
273
+ fields.join('\n\n');
274
+ }
275
+
276
+ if (ast.isImportStatement && ast.isImportStatement(node)) {
277
+ const fields: string[] = [];
278
+ fields.push(`**URI:** \`${node.uri}\``);
279
+ if (node.alias) fields.push(`**Alias:** \`${node.alias}\``);
280
+ return (commentBlock ? `${commentBlock}\n\n---\n\n` : '') +
281
+ `📦 **\`(import)\`**\n\n` +
282
+ fields.join('\n\n');
283
+ }
284
+
285
+ const title = ast.isType(node) ? node.name : node.$type.toLowerCase();
286
+ return (commentBlock ? `${commentBlock}\n\n---\n\n` : '') +
287
+ `ℹ️ **\`(${node.$type.toLowerCase()})\`${ast.isType(node) ? ` ${title}` : ''}**`;
288
+ } catch (error) {
289
+ console.error('Error in getAstNodeHoverContent:', error);
290
+ return 'Unable to display complete information.';
291
+ }
292
+ }
293
+
294
+ private getRefName(ref: ast.Type | Reference<ast.Type> | undefined): string {
295
+ const node = isReference(ref) ? ref.ref : ref;
296
+ if (node && ast.isType(node)) {
297
+ return node.name;
298
+ }
299
+ return '';
300
+ }
301
+
302
+ private refLink(ref: Reference<ast.Type> | ast.Type | undefined, label?: string): string {
303
+ if (label) {
304
+ return `[${label}](#${encodeURIComponent(label)})`;
305
+ }
306
+
307
+ const node = isReference(ref) ? ref.ref : ref;
308
+
309
+ if (node && ast.isType(node)) {
310
+ let linkLabel = node.name;
311
+ try {
312
+ linkLabel = this.qualifiedNameProvider.getQualifiedName(node.$container, node.name);
313
+ } catch {
314
+ // fallback to name
315
+ }
316
+ return `[${linkLabel}](#${encodeURIComponent(linkLabel)})`;
317
+ }
318
+ return '';
319
+ }
320
+
321
+ protected override getKeywordHoverContent(node: AstNode): MaybePromise<Hover | undefined> {
322
+ let comment = isAstNodeWithComment(node) ? node.$comment : undefined;
323
+
324
+ if (!comment) {
325
+ comment = CstUtils.findCommentNode(node.$cstNode, ['ML_COMMENT'])?.text;
326
+ }
327
+
328
+ if (comment && isJSDoc(comment)) {
329
+ const markdown = parseJSDoc(comment).toMarkdown();
330
+ if (markdown) {
331
+ return { contents: { kind: 'markdown', value: markdown } };
332
+ }
333
+ }
334
+
335
+ return undefined;
336
+ }
337
+ }
338
+
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Keyword explanations for DomainLang hover documentation.
3
+ *
4
+ * This dictionary provides fallback hover content for keywords that don't have
5
+ * JSDoc comments in the grammar file, or for providing richer DDD pattern explanations.
6
+ *
7
+ * Basic keyword documentation (domain, boundedcontext, etc.) is now in the grammar file
8
+ * as JSDoc comments. This dictionary focuses on DDD integration patterns and advanced concepts.
9
+ *
10
+ * @see src/language/domain-lang.langium for basic keyword JSDoc
11
+ * @see ddd-pattern-explanations.ts for role patterns and relationship types
12
+ */
13
+ export const keywordExplanations: Record<string, string> = {
14
+ // Advanced syntax keywords
15
+ implements: "**implements** - Declares that a Bounded Context or type implements a Domain or interface.",
16
+ as: "**as** - Used for aliasing or renaming imports or types.",
17
+ from: "**from** - Specifies the source module or file for an import statement.",
18
+ type: "**type** - Declares a new type or alias in the model.",
19
+ map: "**map** - Defines a mapping or transformation between elements.",
20
+ this: "**this** - Refers to the current context or object.",
21
+
22
+ // DDD Classifiers
23
+ entity: "**Entity** - Domain object with distinct identity that runs through time.",
24
+ valueobject: "**Value Object** - Immutable object that describes a characteristic.",
25
+ aggregate: "**Aggregate** - Cluster of domain objects with a root and boundary.",
26
+ service: "**Service** - Stateless domain operation.",
27
+ event: "**Event** - Significant domain occurrence or state change.",
28
+ businessrule: "**Business Rule** - Rule that constrains business behavior.",
29
+
30
+ // DDD Integration Patterns
31
+ acl: "**ACL (Anti-Corruption Layer)** - Translation layer protecting downstream context from upstream changes.",
32
+ ohs: "**OHS (Open Host Service)** - Well-defined protocol providing access to a subsystem.",
33
+ pl: "**PL (Published Language)** - Documented shared language for context communication.",
34
+ cf: "**CF (Conformist)** - Downstream adopts upstream model without translation.",
35
+ bbom: "**BBoM (Big Ball of Mud)** - System with tangled architecture and no clear boundaries.",
36
+ sk: "**SK (Shared Kernel)** - Shared domain model subset requiring coordination.",
37
+ p: "**P (Partnership)** - Teams collaborate closely with shared success/failure.",
38
+
39
+ // DDD Relationship Types
40
+ separateways: "**Separate Ways** - No connection between contexts, each solves problems independently.",
41
+ partnership: "**Partnership** - Teams share risks and rewards with close collaboration.",
42
+ sharedkernel: "**Shared Kernel** - Shared domain model subset requiring coordination.",
43
+ customersupplier: "**Customer-Supplier** - Upstream prioritizes downstream needs.",
44
+ upstreamdownstream: "**Upstream-Downstream** - Upstream changes affect downstream.",
45
+
46
+ // Relationship arrows
47
+ '<->': "**Bidirectional** - Two contexts connected in both directions.",
48
+ '->': "**Upstream → Downstream** - Left depends on right.",
49
+ '<-': "**Downstream ← Upstream** - Right depends on left.",
50
+ };
@@ -0,0 +1,290 @@
1
+ /**
2
+ * Manifest Diagnostics Service for DomainLang.
3
+ *
4
+ * Provides LSP diagnostics for model.yaml files by integrating the ManifestValidator
5
+ * with the VS Code language server protocol.
6
+ *
7
+ * This service:
8
+ * - Validates model.yaml files using ManifestValidator
9
+ * - Converts ManifestDiagnostic to LSP Diagnostic format
10
+ * - Sends diagnostics to the LSP connection
11
+ *
12
+ * @module
13
+ */
14
+
15
+ import type { Connection } from 'vscode-languageserver';
16
+ import { Diagnostic, DiagnosticSeverity, Position, Range } from 'vscode-languageserver-types';
17
+ import YAML, { type Document as YAMLDocument, type YAMLMap, type Pair, isMap, isPair, isScalar } from 'yaml';
18
+ import { ManifestValidator, type ManifestDiagnostic, type ManifestSeverity } from '../validation/manifest.js';
19
+ import type { ModelManifest } from '../services/types.js';
20
+
21
+ /**
22
+ * Service for validating model.yaml and sending diagnostics via LSP.
23
+ */
24
+ export class ManifestDiagnosticsService {
25
+ private readonly validator = new ManifestValidator();
26
+ private connection: Connection | undefined;
27
+
28
+ /**
29
+ * Sets the LSP connection for sending diagnostics.
30
+ * Must be called before validateAndSendDiagnostics.
31
+ */
32
+ setConnection(connection: Connection): void {
33
+ this.connection = connection;
34
+ }
35
+
36
+ /**
37
+ * Validates a model.yaml file and sends diagnostics to the LSP connection.
38
+ *
39
+ * @param manifestUri - URI of the model.yaml file
40
+ * @param content - Raw YAML content of the file
41
+ * @param options - Validation options
42
+ */
43
+ async validateAndSendDiagnostics(
44
+ manifestUri: string,
45
+ content: string,
46
+ options?: { requirePublishable?: boolean }
47
+ ): Promise<void> {
48
+ if (!this.connection) {
49
+ return; // No connection, skip diagnostics
50
+ }
51
+
52
+ const diagnostics = this.validate(content, options);
53
+
54
+ await this.connection.sendDiagnostics({
55
+ uri: manifestUri,
56
+ diagnostics
57
+ });
58
+ }
59
+
60
+ /**
61
+ * Validates manifest content and returns LSP diagnostics.
62
+ *
63
+ * @param content - Raw YAML content
64
+ * @param options - Validation options
65
+ * @returns Array of LSP diagnostics
66
+ */
67
+ validate(
68
+ content: string,
69
+ options?: { requirePublishable?: boolean }
70
+ ): Diagnostic[] {
71
+ // Parse YAML to get both the manifest object and source map
72
+ let yamlDoc: YAMLDocument.Parsed;
73
+ let manifest: ModelManifest;
74
+
75
+ try {
76
+ yamlDoc = YAML.parseDocument(content);
77
+
78
+ // Check for YAML parse errors (they're in the errors array, not thrown)
79
+ if (yamlDoc.errors && yamlDoc.errors.length > 0) {
80
+ return yamlDoc.errors.map(err => ({
81
+ severity: DiagnosticSeverity.Error,
82
+ range: this.yamlErrorToRange(err, content),
83
+ message: `YAML parse error: ${err.message}`,
84
+ source: 'domainlang'
85
+ }));
86
+ }
87
+
88
+ manifest = (yamlDoc.toJSON() ?? {}) as ModelManifest;
89
+ } catch (error) {
90
+ // Fallback for unexpected errors
91
+ const message = error instanceof Error ? error.message : 'Invalid YAML syntax';
92
+ return [{
93
+ severity: DiagnosticSeverity.Error,
94
+ range: Range.create(Position.create(0, 0), Position.create(0, 1)),
95
+ message: `YAML parse error: ${message}`,
96
+ source: 'domainlang'
97
+ }];
98
+ }
99
+
100
+ // Run manifest validation
101
+ const result = this.validator.validate(manifest, options);
102
+
103
+ // Convert to LSP diagnostics with source locations
104
+ return result.diagnostics.map(diag =>
105
+ this.toVSCodeDiagnostic(diag, yamlDoc)
106
+ );
107
+ }
108
+
109
+ /**
110
+ * Converts a YAML error to an LSP Range.
111
+ */
112
+ private yamlErrorToRange(err: YAML.YAMLError, _content: string): Range {
113
+ if (err.linePos && err.linePos.length >= 1) {
114
+ const startPos = err.linePos[0];
115
+ const startLine = startPos.line - 1; // YAML uses 1-based lines
116
+ const startCol = startPos.col - 1; // YAML uses 1-based columns
117
+ const endPos = err.linePos.length >= 2 ? err.linePos[1] : undefined;
118
+ const endLine = endPos ? endPos.line - 1 : startLine;
119
+ const endCol = endPos ? endPos.col - 1 : startCol + 1;
120
+ return Range.create(
121
+ Position.create(startLine, startCol),
122
+ Position.create(endLine, endCol)
123
+ );
124
+ }
125
+ return Range.create(Position.create(0, 0), Position.create(0, 1));
126
+ }
127
+
128
+ /**
129
+ * Clears diagnostics for a manifest file.
130
+ * Call this when the file is closed or deleted.
131
+ */
132
+ async clearDiagnostics(manifestUri: string): Promise<void> {
133
+ if (!this.connection) {
134
+ return;
135
+ }
136
+
137
+ await this.connection.sendDiagnostics({
138
+ uri: manifestUri,
139
+ diagnostics: []
140
+ });
141
+ }
142
+
143
+ /**
144
+ * Converts a ManifestDiagnostic to an LSP Diagnostic.
145
+ */
146
+ private toVSCodeDiagnostic(
147
+ diag: ManifestDiagnostic,
148
+ yamlDoc: YAMLDocument.Parsed
149
+ ): Diagnostic {
150
+ const range = this.findRangeForPath(diag.path, yamlDoc);
151
+
152
+ let message = diag.message;
153
+ if (diag.hint) {
154
+ message += `\nHint: ${diag.hint}`;
155
+ }
156
+
157
+ return {
158
+ severity: this.toVSCodeSeverity(diag.severity),
159
+ range,
160
+ message,
161
+ source: 'domainlang',
162
+ code: diag.code
163
+ };
164
+ }
165
+
166
+ /**
167
+ * Converts ManifestSeverity to LSP DiagnosticSeverity.
168
+ */
169
+ private toVSCodeSeverity(severity: ManifestSeverity): DiagnosticSeverity {
170
+ switch (severity) {
171
+ case 'error':
172
+ return DiagnosticSeverity.Error;
173
+ case 'warning':
174
+ return DiagnosticSeverity.Warning;
175
+ case 'info':
176
+ return DiagnosticSeverity.Information;
177
+ default:
178
+ return DiagnosticSeverity.Warning;
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Finds the source range for a YAML path like "dependencies.core.version".
184
+ * Returns a fallback range at start of file if path not found.
185
+ */
186
+ private findRangeForPath(path: string, yamlDoc: YAMLDocument.Parsed): Range {
187
+ const fallback = Range.create(Position.create(0, 0), Position.create(0, 1));
188
+
189
+ if (!yamlDoc.contents || !isMap(yamlDoc.contents)) {
190
+ return fallback;
191
+ }
192
+
193
+ const parts = path.split('.');
194
+ let currentNode: unknown = yamlDoc.contents;
195
+
196
+ for (const part of parts) {
197
+ if (!isMap(currentNode)) {
198
+ return fallback;
199
+ }
200
+
201
+ const mapNode = currentNode as YAMLMap;
202
+ const item = mapNode.items.find((pair): pair is Pair =>
203
+ isPair(pair) && isScalar(pair.key) && String(pair.key.value) === part
204
+ );
205
+
206
+ if (!item) {
207
+ return fallback;
208
+ }
209
+
210
+ // If this is the last part, return the range of the key
211
+ if (part === parts[parts.length - 1]) {
212
+ const keyNode = item.key;
213
+ if (isScalar(keyNode) && keyNode.range) {
214
+ const [start, end] = keyNode.range;
215
+ return this.offsetsToRange(start, end, yamlDoc.toString());
216
+ }
217
+ }
218
+
219
+ currentNode = item.value;
220
+ }
221
+
222
+ return fallback;
223
+ }
224
+
225
+ /**
226
+ * Converts byte offsets to a VS Code Range using line/column calculation.
227
+ */
228
+ private offsetsToRange(startOffset: number, endOffset: number, content: string): Range {
229
+ const lines = content.split('\n');
230
+ let currentOffset = 0;
231
+ let startLine = 0;
232
+ let startCol = 0;
233
+ let endLine = 0;
234
+ let endCol = 0;
235
+ let foundStart = false;
236
+ let foundEnd = false;
237
+
238
+ for (let lineNum = 0; lineNum < lines.length && !foundEnd; lineNum++) {
239
+ const lineLength = lines[lineNum].length + 1; // +1 for newline
240
+
241
+ if (!foundStart && currentOffset + lineLength > startOffset) {
242
+ startLine = lineNum;
243
+ startCol = startOffset - currentOffset;
244
+ foundStart = true;
245
+ }
246
+
247
+ if (!foundEnd && currentOffset + lineLength >= endOffset) {
248
+ endLine = lineNum;
249
+ endCol = endOffset - currentOffset;
250
+ foundEnd = true;
251
+ }
252
+
253
+ currentOffset += lineLength;
254
+ }
255
+
256
+ return Range.create(
257
+ Position.create(startLine, startCol),
258
+ Position.create(endLine, endCol)
259
+ );
260
+ }
261
+ }
262
+
263
+ /**
264
+ * Singleton instance for use across the language server.
265
+ */
266
+ let manifestDiagnosticsService: ManifestDiagnosticsService | undefined;
267
+
268
+ /**
269
+ * Gets or creates the manifest diagnostics service singleton.
270
+ */
271
+ export function getManifestDiagnosticsService(): ManifestDiagnosticsService {
272
+ if (!manifestDiagnosticsService) {
273
+ manifestDiagnosticsService = new ManifestDiagnosticsService();
274
+ }
275
+ return manifestDiagnosticsService;
276
+ }
277
+
278
+ /**
279
+ * Helper to validate a manifest URI with the given content.
280
+ * Convenience function for use in file watchers.
281
+ */
282
+ export async function validateManifestFile(
283
+ connection: Connection,
284
+ manifestUri: string,
285
+ content: string
286
+ ): Promise<void> {
287
+ const service = getManifestDiagnosticsService();
288
+ service.setConnection(connection);
289
+ await service.validateAndSendDiagnostics(manifestUri, content);
290
+ }
@@ -0,0 +1,15 @@
1
+ import { EmptyFileSystem } from 'langium';
2
+ import { startLanguageServer } from 'langium/lsp';
3
+ import { BrowserMessageReader, BrowserMessageWriter, createConnection } from 'vscode-languageserver/browser.js';
4
+ import { createDomainLangServices } from './domain-lang-module.js';
5
+
6
+ // declare const self: DedicatedWorkerGlobalScope;
7
+
8
+ const messageReader = new BrowserMessageReader(self);
9
+ const messageWriter = new BrowserMessageWriter(self);
10
+
11
+ const connection = createConnection(messageReader, messageWriter);
12
+
13
+ const { shared } = createDomainLangServices({ connection, ...EmptyFileSystem });
14
+
15
+ startLanguageServer(shared);