@domainlang/language 0.1.81

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 (188) hide show
  1. package/README.md +32 -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 +55 -0
  6. package/out/domain-lang-module.js +59 -0
  7. package/out/domain-lang-module.js.map +1 -0
  8. package/out/generated/ast.d.ts +770 -0
  9. package/out/generated/ast.js +565 -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 +2502 -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 +13 -0
  18. package/out/index.js +17 -0
  19. package/out/index.js.map +1 -0
  20. package/out/lsp/domain-lang-completion.d.ts +37 -0
  21. package/out/lsp/domain-lang-completion.js +452 -0
  22. package/out/lsp/domain-lang-completion.js.map +1 -0
  23. package/out/lsp/domain-lang-formatter.d.ts +15 -0
  24. package/out/lsp/domain-lang-formatter.js +43 -0
  25. package/out/lsp/domain-lang-formatter.js.map +1 -0
  26. package/out/lsp/domain-lang-naming.d.ts +34 -0
  27. package/out/lsp/domain-lang-naming.js +49 -0
  28. package/out/lsp/domain-lang-naming.js.map +1 -0
  29. package/out/lsp/domain-lang-scope.d.ts +59 -0
  30. package/out/lsp/domain-lang-scope.js +102 -0
  31. package/out/lsp/domain-lang-scope.js.map +1 -0
  32. package/out/lsp/hover/ddd-pattern-explanations.d.ts +50 -0
  33. package/out/lsp/hover/ddd-pattern-explanations.js +196 -0
  34. package/out/lsp/hover/ddd-pattern-explanations.js.map +1 -0
  35. package/out/lsp/hover/domain-lang-hover.d.ts +19 -0
  36. package/out/lsp/hover/domain-lang-hover.js +306 -0
  37. package/out/lsp/hover/domain-lang-hover.js.map +1 -0
  38. package/out/lsp/hover/domain-lang-keywords.d.ts +13 -0
  39. package/out/lsp/hover/domain-lang-keywords.js +47 -0
  40. package/out/lsp/hover/domain-lang-keywords.js.map +1 -0
  41. package/out/main-browser.d.ts +1 -0
  42. package/out/main-browser.js +11 -0
  43. package/out/main-browser.js.map +1 -0
  44. package/out/main.d.ts +1 -0
  45. package/out/main.js +74 -0
  46. package/out/main.js.map +1 -0
  47. package/out/sdk/ast-augmentation.d.ts +136 -0
  48. package/out/sdk/ast-augmentation.js +62 -0
  49. package/out/sdk/ast-augmentation.js.map +1 -0
  50. package/out/sdk/index.d.ts +94 -0
  51. package/out/sdk/index.js +97 -0
  52. package/out/sdk/index.js.map +1 -0
  53. package/out/sdk/indexes.d.ts +16 -0
  54. package/out/sdk/indexes.js +97 -0
  55. package/out/sdk/indexes.js.map +1 -0
  56. package/out/sdk/loader-node.d.ts +47 -0
  57. package/out/sdk/loader-node.js +104 -0
  58. package/out/sdk/loader-node.js.map +1 -0
  59. package/out/sdk/loader.d.ts +49 -0
  60. package/out/sdk/loader.js +85 -0
  61. package/out/sdk/loader.js.map +1 -0
  62. package/out/sdk/patterns.d.ts +93 -0
  63. package/out/sdk/patterns.js +123 -0
  64. package/out/sdk/patterns.js.map +1 -0
  65. package/out/sdk/query.d.ts +90 -0
  66. package/out/sdk/query.js +679 -0
  67. package/out/sdk/query.js.map +1 -0
  68. package/out/sdk/resolution.d.ts +52 -0
  69. package/out/sdk/resolution.js +68 -0
  70. package/out/sdk/resolution.js.map +1 -0
  71. package/out/sdk/types.d.ts +301 -0
  72. package/out/sdk/types.js +8 -0
  73. package/out/sdk/types.js.map +1 -0
  74. package/out/services/dependency-analyzer.d.ts +94 -0
  75. package/out/services/dependency-analyzer.js +279 -0
  76. package/out/services/dependency-analyzer.js.map +1 -0
  77. package/out/services/dependency-resolver.d.ts +123 -0
  78. package/out/services/dependency-resolver.js +252 -0
  79. package/out/services/dependency-resolver.js.map +1 -0
  80. package/out/services/git-url-resolver.browser.d.ts +18 -0
  81. package/out/services/git-url-resolver.browser.js +15 -0
  82. package/out/services/git-url-resolver.browser.js.map +1 -0
  83. package/out/services/git-url-resolver.d.ts +192 -0
  84. package/out/services/git-url-resolver.js +382 -0
  85. package/out/services/git-url-resolver.js.map +1 -0
  86. package/out/services/governance-validator.d.ts +80 -0
  87. package/out/services/governance-validator.js +159 -0
  88. package/out/services/governance-validator.js.map +1 -0
  89. package/out/services/import-resolver.d.ts +18 -0
  90. package/out/services/import-resolver.js +22 -0
  91. package/out/services/import-resolver.js.map +1 -0
  92. package/out/services/performance-optimizer.d.ts +60 -0
  93. package/out/services/performance-optimizer.js +140 -0
  94. package/out/services/performance-optimizer.js.map +1 -0
  95. package/out/services/relationship-inference.d.ts +11 -0
  96. package/out/services/relationship-inference.js +98 -0
  97. package/out/services/relationship-inference.js.map +1 -0
  98. package/out/services/workspace-manager.d.ts +76 -0
  99. package/out/services/workspace-manager.js +323 -0
  100. package/out/services/workspace-manager.js.map +1 -0
  101. package/out/syntaxes/domain-lang.monarch.d.ts +76 -0
  102. package/out/syntaxes/domain-lang.monarch.js +29 -0
  103. package/out/syntaxes/domain-lang.monarch.js.map +1 -0
  104. package/out/utils/import-utils.d.ts +57 -0
  105. package/out/utils/import-utils.js +228 -0
  106. package/out/utils/import-utils.js.map +1 -0
  107. package/out/validation/bounded-context.d.ts +11 -0
  108. package/out/validation/bounded-context.js +79 -0
  109. package/out/validation/bounded-context.js.map +1 -0
  110. package/out/validation/classification.d.ts +3 -0
  111. package/out/validation/classification.js +3 -0
  112. package/out/validation/classification.js.map +1 -0
  113. package/out/validation/constants.d.ts +77 -0
  114. package/out/validation/constants.js +96 -0
  115. package/out/validation/constants.js.map +1 -0
  116. package/out/validation/domain-lang-validator.d.ts +2 -0
  117. package/out/validation/domain-lang-validator.js +27 -0
  118. package/out/validation/domain-lang-validator.js.map +1 -0
  119. package/out/validation/domain.d.ts +11 -0
  120. package/out/validation/domain.js +18 -0
  121. package/out/validation/domain.js.map +1 -0
  122. package/out/validation/import.d.ts +44 -0
  123. package/out/validation/import.js +135 -0
  124. package/out/validation/import.js.map +1 -0
  125. package/out/validation/maps.d.ts +21 -0
  126. package/out/validation/maps.js +56 -0
  127. package/out/validation/maps.js.map +1 -0
  128. package/out/validation/metadata.d.ts +7 -0
  129. package/out/validation/metadata.js +12 -0
  130. package/out/validation/metadata.js.map +1 -0
  131. package/out/validation/model.d.ts +12 -0
  132. package/out/validation/model.js +29 -0
  133. package/out/validation/model.js.map +1 -0
  134. package/out/validation/relationships.d.ts +12 -0
  135. package/out/validation/relationships.js +94 -0
  136. package/out/validation/relationships.js.map +1 -0
  137. package/out/validation/shared.d.ts +6 -0
  138. package/out/validation/shared.js +12 -0
  139. package/out/validation/shared.js.map +1 -0
  140. package/package.json +97 -0
  141. package/src/ast-augmentation.ts +5 -0
  142. package/src/domain-lang-module.ts +100 -0
  143. package/src/domain-lang.langium +356 -0
  144. package/src/generated/ast.ts +999 -0
  145. package/src/generated/grammar.ts +2504 -0
  146. package/src/generated/module.ts +25 -0
  147. package/src/index.ts +17 -0
  148. package/src/lsp/domain-lang-completion.ts +514 -0
  149. package/src/lsp/domain-lang-formatter.ts +51 -0
  150. package/src/lsp/domain-lang-naming.ts +56 -0
  151. package/src/lsp/domain-lang-scope.ts +137 -0
  152. package/src/lsp/hover/ddd-pattern-explanations.ts +237 -0
  153. package/src/lsp/hover/domain-lang-hover.ts +340 -0
  154. package/src/lsp/hover/domain-lang-keywords.ts +50 -0
  155. package/src/main-browser.ts +15 -0
  156. package/src/main.ts +85 -0
  157. package/src/sdk/README.md +297 -0
  158. package/src/sdk/ast-augmentation.ts +157 -0
  159. package/src/sdk/index.ts +128 -0
  160. package/src/sdk/indexes.ts +155 -0
  161. package/src/sdk/loader-node.ts +126 -0
  162. package/src/sdk/loader.ts +99 -0
  163. package/src/sdk/patterns.ts +147 -0
  164. package/src/sdk/query.ts +802 -0
  165. package/src/sdk/resolution.ts +78 -0
  166. package/src/sdk/types.ts +346 -0
  167. package/src/services/dependency-analyzer.ts +381 -0
  168. package/src/services/dependency-resolver.ts +334 -0
  169. package/src/services/git-url-resolver.browser.ts +31 -0
  170. package/src/services/git-url-resolver.ts +524 -0
  171. package/src/services/governance-validator.ts +219 -0
  172. package/src/services/import-resolver.ts +30 -0
  173. package/src/services/performance-optimizer.ts +170 -0
  174. package/src/services/relationship-inference.ts +121 -0
  175. package/src/services/workspace-manager.ts +416 -0
  176. package/src/syntaxes/domain-lang.monarch.ts +29 -0
  177. package/src/utils/import-utils.ts +274 -0
  178. package/src/validation/bounded-context.ts +99 -0
  179. package/src/validation/classification.ts +5 -0
  180. package/src/validation/constants.ts +124 -0
  181. package/src/validation/domain-lang-validator.ts +33 -0
  182. package/src/validation/domain.ts +24 -0
  183. package/src/validation/import.ts +171 -0
  184. package/src/validation/maps.ts +72 -0
  185. package/src/validation/metadata.ts +14 -0
  186. package/src/validation/model.ts +37 -0
  187. package/src/validation/relationships.ts +154 -0
  188. package/src/validation/shared.ts +14 -0
@@ -0,0 +1,171 @@
1
+ import type { ValidationAcceptor, ValidationChecks } from 'langium';
2
+ import type { DomainLangAstType, ImportStatement, Model } from '../generated/ast.js';
3
+ import { resolveImportPath } from '../utils/import-utils.js';
4
+ import type { DomainLangServices } from '../domain-lang-module.js';
5
+ import type { LangiumDocument } from 'langium';
6
+
7
+ /**
8
+ * Validates import statements in DomainLang.
9
+ *
10
+ * Checks:
11
+ * - Import paths are resolvable
12
+ * - Named imports exist in target document
13
+ * - Import aliases don't conflict with local names
14
+ */
15
+ export class ImportValidator {
16
+ private readonly documents: DomainLangServices['shared']['workspace']['LangiumDocuments'];
17
+
18
+ constructor(services: DomainLangServices) {
19
+ this.documents = services.shared.workspace.LangiumDocuments;
20
+ }
21
+
22
+ /**
23
+ * Validates that an import path is resolvable.
24
+ */
25
+ async checkImportPath(
26
+ imp: ImportStatement,
27
+ accept: ValidationAcceptor,
28
+ document: LangiumDocument
29
+ ): Promise<void> {
30
+ if (!imp.uri) {
31
+ accept('error', 'Import statement must have a URI', {
32
+ node: imp,
33
+ keyword: 'import'
34
+ });
35
+ return;
36
+ }
37
+
38
+ try {
39
+ await resolveImportPath(document, imp.uri);
40
+ } catch (error) {
41
+ const message = error instanceof Error ? error.message : String(error);
42
+ accept('error', `Cannot resolve import: ${message}`, {
43
+ node: imp,
44
+ property: 'uri'
45
+ });
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Validates that named imports exist in the target document.
51
+ */
52
+ async checkNamedImports(
53
+ imp: ImportStatement,
54
+ accept: ValidationAcceptor,
55
+ document: LangiumDocument
56
+ ): Promise<void> {
57
+ // Only check if we have named imports
58
+ if (!imp.symbols || imp.symbols.length === 0) {
59
+ return;
60
+ }
61
+
62
+ if (!imp.uri) {
63
+ return; // Already reported by checkImportPath
64
+ }
65
+
66
+ try {
67
+ // Resolve the target document
68
+ const targetUri = await resolveImportPath(document, imp.uri);
69
+ const targetDoc = await this.documents.getOrCreateDocument(targetUri);
70
+
71
+ if (!targetDoc.parseResult?.value) {
72
+ accept('error', `Cannot load imported document: ${imp.uri}`, {
73
+ node: imp,
74
+ property: 'uri'
75
+ });
76
+ return;
77
+ }
78
+
79
+ // Get all exported symbols from target document
80
+ const targetModel = targetDoc.parseResult.value as Model;
81
+ const exportedSymbols = this.getExportedSymbols(targetModel);
82
+
83
+ // Check each imported symbol
84
+ for (const symbol of imp.symbols) {
85
+ if (!exportedSymbols.has(symbol)) {
86
+ accept('error',
87
+ `Symbol '${symbol}' not found in ${imp.uri}`,
88
+ {
89
+ node: imp,
90
+ property: 'symbols'
91
+ }
92
+ );
93
+ }
94
+ }
95
+ } catch {
96
+ // Import path error already reported by checkImportPath
97
+ return;
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Gets all exportable symbols from a model.
103
+ *
104
+ * In DomainLang, top-level declarations are implicitly exported:
105
+ * - Domains
106
+ * - BoundedContexts
107
+ * - Classifications
108
+ * - Groups
109
+ */
110
+ private getExportedSymbols(model: Model): Set<string> {
111
+ const symbols = new Set<string>();
112
+
113
+ // Iterate through all structure elements
114
+ for (const element of model.children ?? []) {
115
+ // Check if element has a name and add it
116
+ if ('name' in element && typeof element.name === 'string') {
117
+ symbols.add(element.name);
118
+ }
119
+ }
120
+
121
+ return symbols;
122
+ }
123
+
124
+ /**
125
+ * Checks for unused imports.
126
+ *
127
+ * This is a warning, not an error, to avoid being too strict.
128
+ */
129
+ checkUnusedImports(
130
+ imp: ImportStatement,
131
+ _accept: ValidationAcceptor,
132
+ _model: Model
133
+ ): void {
134
+ // Skip check for wildcard imports (no named imports)
135
+ if (!imp.symbols || imp.symbols.length === 0) {
136
+ return;
137
+ }
138
+
139
+ // For now, just a placeholder - would require tracking symbol usage
140
+ // across the entire document, which is complex
141
+ // TODO: Implement symbol usage tracking
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Creates validation checks for import statements.
147
+ */
148
+ export function createImportChecks(_services: DomainLangServices): ValidationChecks<DomainLangAstType> {
149
+
150
+ return {
151
+ ImportStatement: (imp, accept) => {
152
+ const document = imp.$document;
153
+ if (!document) return;
154
+
155
+ // Note: Langium's validation is synchronous, so async checks won't
156
+ // execute during document validation. These checks will run during
157
+ // the build phase when documents are fully loaded.
158
+
159
+ // For now, just do basic syntax validation
160
+ if (!imp.uri) {
161
+ accept('error', 'Import statement must have a URI', {
162
+ node: imp,
163
+ keyword: 'import'
164
+ });
165
+ }
166
+
167
+ // TODO: Implement async validation in a separate build phase
168
+ // This would require using DocumentBuilder.onBuildPhase() or similar
169
+ }
170
+ };
171
+ }
@@ -0,0 +1,72 @@
1
+ import type { ValidationAcceptor } from 'langium';
2
+ import type { ContextMap, DomainMap } from '../generated/ast.js';
3
+
4
+ /**
5
+ * Validates that a context map contains at least one bounded context.
6
+ * Empty context maps are not useful for documentation purposes.
7
+ *
8
+ * @param map - The context map to validate
9
+ * @param accept - The validation acceptor for reporting issues
10
+ */
11
+ function validateContextMapHasContexts(
12
+ map: ContextMap,
13
+ accept: ValidationAcceptor
14
+ ): void {
15
+ if (!map.boundedContexts || map.boundedContexts.length === 0) {
16
+ accept('warning', `Context Map '${map.name}' contains no bounded contexts`, {
17
+ node: map,
18
+ keyword: 'contains'
19
+ });
20
+ }
21
+ }
22
+
23
+ /**
24
+ * Validates that a context map has at least one relationship if it contains multiple contexts.
25
+ * Multiple unrelated contexts should have documented relationships.
26
+ *
27
+ * @param map - The context map to validate
28
+ * @param accept - The validation acceptor for reporting issues
29
+ */
30
+ function validateContextMapHasRelationships(
31
+ map: ContextMap,
32
+ accept: ValidationAcceptor
33
+ ): void {
34
+ const contextCount = map.boundedContexts?.length ?? 0;
35
+ const relationshipCount = map.relationships?.length ?? 0;
36
+
37
+ // Only warn if multiple contexts exist without relationships
38
+ if (contextCount > 1 && relationshipCount === 0) {
39
+ accept('info', `Context Map '${map.name}' contains ${contextCount} contexts but no documented relationships`, {
40
+ node: map,
41
+ keyword: 'ContextMap'
42
+ });
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Validates that a domain map contains at least one domain.
48
+ * Empty domain maps are not useful for documentation purposes.
49
+ *
50
+ * @param map - The domain map to validate
51
+ * @param accept - The validation acceptor for reporting issues
52
+ */
53
+ function validateDomainMapHasDomains(
54
+ map: DomainMap,
55
+ accept: ValidationAcceptor
56
+ ): void {
57
+ if (!map.domains || map.domains.length === 0) {
58
+ accept('warning', `Domain Map '${map.name}' contains no domains`, {
59
+ node: map,
60
+ keyword: 'contains'
61
+ });
62
+ }
63
+ }
64
+
65
+ export const contextMapChecks = [
66
+ validateContextMapHasContexts,
67
+ validateContextMapHasRelationships
68
+ ];
69
+
70
+ export const domainMapChecks = [
71
+ validateDomainMapHasDomains
72
+ ];
@@ -0,0 +1,14 @@
1
+ import type { ValidationChecks } from 'langium';
2
+ import type { Metadata, DomainLangAstType } from '../generated/ast.js';
3
+
4
+ /**
5
+ * Validation checks for Metadata elements.
6
+ * - Ensures metadata keys are defined before use
7
+ */
8
+ export const metadataChecks: ValidationChecks<DomainLangAstType> = {
9
+ Metadata(metadata: Metadata, accept) {
10
+ if (!metadata.name) {
11
+ accept('error', 'Metadata must have a name', { node: metadata });
12
+ }
13
+ },
14
+ };
@@ -0,0 +1,37 @@
1
+ import { setInferredRelationshipTypes } from '../services/relationship-inference.js';
2
+ import { extractNames } from './shared.js';
3
+ import type { ValidationAcceptor } from 'langium';
4
+ import type { Model } from '../generated/ast.js';
5
+ import { ValidationMessages, buildCodeDescription } from './constants.js';
6
+
7
+ /**
8
+ * Validates that all elements in the model have unique fully qualified names.
9
+ * Also triggers relationship type inference.
10
+ *
11
+ * @param model - The model to validate
12
+ * @param accept - The validation acceptor for reporting issues
13
+ */
14
+ function validateModelUniqueNames(
15
+ model: Model,
16
+ accept: ValidationAcceptor
17
+ ): void {
18
+ const uniqueNames = new Set<string>();
19
+ const names = extractNames(model);
20
+
21
+ for (const {fqn, node} of names) {
22
+ const oldSize = uniqueNames.size;
23
+ uniqueNames.add(fqn);
24
+ if (uniqueNames.size === oldSize) {
25
+ accept('error', ValidationMessages.DUPLICATE_ELEMENT(fqn), {
26
+ node: node,
27
+ property: 'name',
28
+ codeDescription: buildCodeDescription('language.md', 'naming')
29
+ });
30
+ }
31
+ }
32
+
33
+ // Enrich relationships with inferred types
34
+ setInferredRelationshipTypes(model);
35
+ }
36
+
37
+ export const modelChecks = [validateModelUniqueNames];
@@ -0,0 +1,154 @@
1
+ import type { ValidationAcceptor } from 'langium';
2
+ import type { Relationship, BoundedContextRef } from '../generated/ast.js';
3
+ import { isThisRef } from '../generated/ast.js';
4
+ import { ValidationMessages, buildCodeDescription } from './constants.js';
5
+
6
+ /**
7
+ * Gets a display name for a BoundedContextRef (handles 'this' and regular refs).
8
+ */
9
+ function getContextName(ref: BoundedContextRef): string {
10
+ if (isThisRef(ref)) {
11
+ return 'this';
12
+ }
13
+ return ref.link?.$refText ?? 'unknown';
14
+ }
15
+
16
+ /**
17
+ * Validates that SharedKernel patterns use bidirectional relationships.
18
+ * SharedKernel implies mutual dependency and shared code ownership.
19
+ *
20
+ * @param relationship - The relationship to validate
21
+ * @param accept - The validation acceptor for reporting issues
22
+ */
23
+ function validateSharedKernelBidirectional(
24
+ relationship: Relationship,
25
+ accept: ValidationAcceptor
26
+ ): void {
27
+ // Check if SharedKernel pattern exists on either side
28
+ const hasSharedKernelLeft = relationship.leftPatterns?.some(
29
+ pattern => pattern === 'SK' || pattern === 'SharedKernel'
30
+ );
31
+ const hasSharedKernelRight = relationship.rightPatterns?.some(
32
+ pattern => pattern === 'SK' || pattern === 'SharedKernel'
33
+ );
34
+
35
+ if ((hasSharedKernelLeft || hasSharedKernelRight) && relationship.arrow !== '<->') {
36
+ const leftName = getContextName(relationship.left);
37
+ const rightName = getContextName(relationship.right);
38
+
39
+ accept('warning',
40
+ ValidationMessages.SHARED_KERNEL_MUST_BE_BIDIRECTIONAL(leftName, rightName, relationship.arrow),
41
+ { node: relationship, property: 'arrow', codeDescription: buildCodeDescription('language.md', 'integration-patterns') }
42
+ );
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Validates that Anti-Corruption Layer (ACL) is on the consuming side.
48
+ * ACL protects downstream context from upstream changes.
49
+ *
50
+ * @param relationship - The relationship to validate
51
+ * @param accept - The validation acceptor for reporting issues
52
+ */
53
+ function validateACLPlacement(
54
+ relationship: Relationship,
55
+ accept: ValidationAcceptor
56
+ ): void {
57
+ const hasACLLeft = relationship.leftPatterns?.some(
58
+ pattern => pattern === 'ACL' || pattern === 'AntiCorruptionLayer'
59
+ );
60
+ const hasACLRight = relationship.rightPatterns?.some(
61
+ pattern => pattern === 'ACL' || pattern === 'AntiCorruptionLayer'
62
+ );
63
+
64
+ // ACL on left side with -> arrow means upstream has ACL (incorrect)
65
+ if (hasACLLeft && relationship.arrow === '->') {
66
+ const leftName = getContextName(relationship.left);
67
+ accept('warning',
68
+ ValidationMessages.ACL_ON_WRONG_SIDE(leftName, 'left'),
69
+ { node: relationship, property: 'leftPatterns', codeDescription: buildCodeDescription('language.md', 'integration-patterns') }
70
+ );
71
+ }
72
+
73
+ // ACL on right side with <- arrow means upstream has ACL (incorrect)
74
+ if (hasACLRight && relationship.arrow === '<-') {
75
+ const rightName = getContextName(relationship.right);
76
+ accept('warning',
77
+ ValidationMessages.ACL_ON_WRONG_SIDE(rightName, 'right'),
78
+ { node: relationship, property: 'rightPatterns', codeDescription: buildCodeDescription('language.md', 'integration-patterns') }
79
+ );
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Validates that Conformist pattern is on the consuming side.
85
+ * Conformist means accepting upstream model without translation.
86
+ *
87
+ * @param relationship - The relationship to validate
88
+ * @param accept - The validation acceptor for reporting issues
89
+ */
90
+ function validateConformistPlacement(
91
+ relationship: Relationship,
92
+ accept: ValidationAcceptor
93
+ ): void {
94
+ const hasCFLeft = relationship.leftPatterns?.some(
95
+ pattern => pattern === 'CF' || pattern === 'Conformist'
96
+ );
97
+ const hasCFRight = relationship.rightPatterns?.some(
98
+ pattern => pattern === 'CF' || pattern === 'Conformist'
99
+ );
100
+
101
+ // CF on left side with -> arrow means upstream is conformist (incorrect)
102
+ if (hasCFLeft && relationship.arrow === '->') {
103
+ const leftName = getContextName(relationship.left);
104
+ accept('warning',
105
+ ValidationMessages.CONFORMIST_ON_WRONG_SIDE(leftName, 'left'),
106
+ { node: relationship, property: 'leftPatterns', codeDescription: buildCodeDescription('language.md', 'integration-patterns') }
107
+ );
108
+ }
109
+
110
+ // CF on right side with <- arrow means upstream is conformist (incorrect)
111
+ if (hasCFRight && relationship.arrow === '<-') {
112
+ const rightName = getContextName(relationship.right);
113
+ accept('warning',
114
+ ValidationMessages.CONFORMIST_ON_WRONG_SIDE(rightName, 'right'),
115
+ { node: relationship, property: 'rightPatterns', codeDescription: buildCodeDescription('language.md', 'integration-patterns') }
116
+ );
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Validates that relationships don't have too many integration patterns.
122
+ * More than 3 patterns on one side suggests syntax confusion.
123
+ *
124
+ * @param relationship - The relationship to validate
125
+ * @param accept - The validation acceptor for reporting issues
126
+ */
127
+ function validatePatternCount(
128
+ relationship: Relationship,
129
+ accept: ValidationAcceptor
130
+ ): void {
131
+ const leftCount = relationship.leftPatterns?.length ?? 0;
132
+ const rightCount = relationship.rightPatterns?.length ?? 0;
133
+
134
+ if (leftCount > 3) {
135
+ accept('info',
136
+ ValidationMessages.TOO_MANY_PATTERNS(leftCount, 'left'),
137
+ { node: relationship, property: 'leftPatterns', codeDescription: buildCodeDescription('language.md', 'integration-patterns') }
138
+ );
139
+ }
140
+
141
+ if (rightCount > 3) {
142
+ accept('info',
143
+ ValidationMessages.TOO_MANY_PATTERNS(rightCount, 'right'),
144
+ { node: relationship, property: 'rightPatterns', codeDescription: buildCodeDescription('language.md', 'integration-patterns') }
145
+ );
146
+ }
147
+ }
148
+
149
+ export const relationshipChecks = [
150
+ validateSharedKernelBidirectional,
151
+ validateACLPlacement,
152
+ validateConformistPlacement,
153
+ validatePatternCount
154
+ ];
@@ -0,0 +1,14 @@
1
+ import type { AstNode } from 'langium';
2
+ import type { Container } from '../generated/ast.js';
3
+ import { isContainer } from '../generated/ast.js';
4
+ import { QualifiedNameProvider } from '../lsp/domain-lang-naming.js';
5
+
6
+ export function* extractNames(element: Container): Generator<{fqn: string, node: AstNode}> {
7
+ const fqnProvider = new QualifiedNameProvider();
8
+ for (const child of element.children) {
9
+ yield {fqn: fqnProvider.getQualifiedName(child.$container, child.name), node: child};
10
+ if (isContainer(child)) {
11
+ yield* extractNames(child);
12
+ }
13
+ }
14
+ }