@domainlang/language 0.5.2 → 0.7.0

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 (116) hide show
  1. package/README.md +1 -1
  2. package/out/domain-lang-module.js +5 -1
  3. package/out/domain-lang-module.js.map +1 -1
  4. package/out/generated/ast.d.ts +24 -0
  5. package/out/generated/ast.js.map +1 -1
  6. package/out/generated/grammar.js +22 -32
  7. package/out/generated/grammar.js.map +1 -1
  8. package/out/index.d.ts +2 -5
  9. package/out/index.js +10 -6
  10. package/out/index.js.map +1 -1
  11. package/out/lsp/domain-lang-code-actions.js +14 -8
  12. package/out/lsp/domain-lang-code-actions.js.map +1 -1
  13. package/out/lsp/domain-lang-completion.d.ts +3 -0
  14. package/out/lsp/domain-lang-completion.js +41 -13
  15. package/out/lsp/domain-lang-completion.js.map +1 -1
  16. package/out/lsp/domain-lang-formatter.js +24 -18
  17. package/out/lsp/domain-lang-formatter.js.map +1 -1
  18. package/out/lsp/domain-lang-index-manager.d.ts +170 -0
  19. package/out/lsp/domain-lang-index-manager.js +389 -0
  20. package/out/lsp/domain-lang-index-manager.js.map +1 -0
  21. package/out/lsp/domain-lang-scope-provider.d.ts +67 -0
  22. package/out/lsp/domain-lang-scope-provider.js +95 -0
  23. package/out/lsp/domain-lang-scope-provider.js.map +1 -0
  24. package/out/lsp/domain-lang-scope.js +31 -17
  25. package/out/lsp/domain-lang-scope.js.map +1 -1
  26. package/out/lsp/domain-lang-workspace-manager.d.ts +76 -9
  27. package/out/lsp/domain-lang-workspace-manager.js +176 -54
  28. package/out/lsp/domain-lang-workspace-manager.js.map +1 -1
  29. package/out/lsp/hover/domain-lang-hover.d.ts +45 -1
  30. package/out/lsp/hover/domain-lang-hover.js +308 -232
  31. package/out/lsp/hover/domain-lang-hover.js.map +1 -1
  32. package/out/lsp/hover/domain-lang-keywords.d.ts +3 -7
  33. package/out/lsp/hover/domain-lang-keywords.js +115 -38
  34. package/out/lsp/hover/domain-lang-keywords.js.map +1 -1
  35. package/out/lsp/manifest-diagnostics.js +95 -50
  36. package/out/lsp/manifest-diagnostics.js.map +1 -1
  37. package/out/main.js +204 -17
  38. package/out/main.js.map +1 -1
  39. package/out/services/import-resolver.d.ts +39 -2
  40. package/out/services/import-resolver.js +77 -12
  41. package/out/services/import-resolver.js.map +1 -1
  42. package/out/services/types.d.ts +2 -2
  43. package/out/services/workspace-manager.d.ts +33 -31
  44. package/out/services/workspace-manager.js +92 -148
  45. package/out/services/workspace-manager.js.map +1 -1
  46. package/out/utils/document-utils.d.ts +41 -0
  47. package/out/utils/document-utils.js +64 -0
  48. package/out/utils/document-utils.js.map +1 -0
  49. package/out/utils/import-utils.d.ts +0 -17
  50. package/out/utils/import-utils.js +2 -32
  51. package/out/utils/import-utils.js.map +1 -1
  52. package/out/utils/manifest-utils.d.ts +56 -0
  53. package/out/utils/manifest-utils.js +119 -0
  54. package/out/utils/manifest-utils.js.map +1 -0
  55. package/out/validation/constants.d.ts +13 -0
  56. package/out/validation/constants.js +18 -0
  57. package/out/validation/constants.js.map +1 -1
  58. package/out/validation/import.d.ts +12 -2
  59. package/out/validation/import.js +95 -22
  60. package/out/validation/import.js.map +1 -1
  61. package/out/validation/maps.js +51 -2
  62. package/out/validation/maps.js.map +1 -1
  63. package/package.json +1 -1
  64. package/src/domain-lang-module.ts +6 -1
  65. package/src/domain-lang.langium +37 -13
  66. package/src/generated/ast.ts +24 -0
  67. package/src/generated/grammar.ts +22 -32
  68. package/src/index.ts +12 -6
  69. package/src/lsp/domain-lang-code-actions.ts +13 -8
  70. package/src/lsp/domain-lang-completion.ts +61 -13
  71. package/src/lsp/domain-lang-formatter.ts +28 -23
  72. package/src/lsp/domain-lang-index-manager.ts +447 -0
  73. package/src/lsp/domain-lang-scope-provider.ts +134 -0
  74. package/src/lsp/domain-lang-scope.ts +29 -17
  75. package/src/lsp/domain-lang-workspace-manager.ts +201 -53
  76. package/src/lsp/hover/domain-lang-hover.ts +332 -226
  77. package/src/lsp/hover/domain-lang-keywords.ts +129 -43
  78. package/src/lsp/manifest-diagnostics.ts +100 -59
  79. package/src/main.ts +258 -16
  80. package/src/services/import-resolver.ts +91 -12
  81. package/src/services/types.ts +2 -2
  82. package/src/services/workspace-manager.ts +101 -175
  83. package/src/utils/document-utils.ts +80 -0
  84. package/src/utils/import-utils.ts +2 -40
  85. package/src/utils/manifest-utils.ts +132 -0
  86. package/src/validation/constants.ts +24 -0
  87. package/src/validation/import.ts +107 -24
  88. package/src/validation/maps.ts +59 -2
  89. package/out/lsp/hover/ddd-pattern-explanations.d.ts +0 -50
  90. package/out/lsp/hover/ddd-pattern-explanations.js +0 -196
  91. package/out/lsp/hover/ddd-pattern-explanations.js.map +0 -1
  92. package/out/services/dependency-analyzer.d.ts +0 -58
  93. package/out/services/dependency-analyzer.js +0 -254
  94. package/out/services/dependency-analyzer.js.map +0 -1
  95. package/out/services/dependency-resolver.d.ts +0 -146
  96. package/out/services/dependency-resolver.js +0 -452
  97. package/out/services/dependency-resolver.js.map +0 -1
  98. package/out/services/git-url-resolver.browser.d.ts +0 -10
  99. package/out/services/git-url-resolver.browser.js +0 -19
  100. package/out/services/git-url-resolver.browser.js.map +0 -1
  101. package/out/services/git-url-resolver.d.ts +0 -158
  102. package/out/services/git-url-resolver.js +0 -416
  103. package/out/services/git-url-resolver.js.map +0 -1
  104. package/out/services/governance-validator.d.ts +0 -44
  105. package/out/services/governance-validator.js +0 -153
  106. package/out/services/governance-validator.js.map +0 -1
  107. package/out/services/semver.d.ts +0 -98
  108. package/out/services/semver.js +0 -195
  109. package/out/services/semver.js.map +0 -1
  110. package/src/lsp/hover/ddd-pattern-explanations.ts +0 -237
  111. package/src/services/dependency-analyzer.ts +0 -321
  112. package/src/services/dependency-resolver.ts +0 -551
  113. package/src/services/git-url-resolver.browser.ts +0 -26
  114. package/src/services/git-url-resolver.ts +0 -517
  115. package/src/services/governance-validator.ts +0 -177
  116. package/src/services/semver.ts +0 -213
@@ -0,0 +1,132 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs/promises';
3
+ import YAML from 'yaml';
4
+ import type { ModelManifest } from '../services/types.js';
5
+
6
+ const DEFAULT_MANIFEST_FILENAME = 'model.yaml';
7
+ const DEFAULT_ENTRY_FILE = 'index.dlang';
8
+
9
+ /**
10
+ * Checks if a file exists at the given path.
11
+ */
12
+ export async function fileExists(targetPath: string): Promise<boolean> {
13
+ try {
14
+ await fs.access(targetPath);
15
+ return true;
16
+ } catch (error) {
17
+ if ((error as NodeJS.ErrnoException)?.code === 'ENOENT') {
18
+ return false;
19
+ }
20
+ throw error;
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Finds the nearest model.yaml manifest by walking up from startPath.
26
+ *
27
+ * @param startPath - Directory to start searching from
28
+ * @returns Absolute path to model.yaml, or undefined if not found
29
+ */
30
+ export async function findNearestManifest(startPath: string): Promise<string | undefined> {
31
+ let current = path.resolve(startPath);
32
+ const { root } = path.parse(current);
33
+
34
+ while (true) {
35
+ const candidate = path.join(current, DEFAULT_MANIFEST_FILENAME);
36
+ if (await fileExists(candidate)) {
37
+ return candidate;
38
+ }
39
+
40
+ if (current === root) {
41
+ return undefined;
42
+ }
43
+
44
+ const parent = path.dirname(current);
45
+ if (parent === current) {
46
+ return undefined;
47
+ }
48
+ current = parent;
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Finds the workspace root (directory containing model.yaml).
54
+ *
55
+ * @param startPath - Directory to start searching from
56
+ * @returns Absolute path to workspace root, or undefined if no manifest found
57
+ */
58
+ export async function findWorkspaceRoot(startPath: string): Promise<string | undefined> {
59
+ const manifestPath = await findNearestManifest(startPath);
60
+ return manifestPath ? path.dirname(manifestPath) : undefined;
61
+ }
62
+
63
+ /**
64
+ * Reads and parses a model.yaml manifest.
65
+ *
66
+ * @param manifestPath - Absolute path to model.yaml
67
+ * @returns Parsed manifest, or undefined if file doesn't exist
68
+ * @throws Error if file exists but cannot be parsed
69
+ */
70
+ export async function readManifest(manifestPath: string): Promise<ModelManifest | undefined> {
71
+ try {
72
+ const content = await fs.readFile(manifestPath, 'utf-8');
73
+ return (YAML.parse(content) ?? {}) as ModelManifest;
74
+ } catch (error) {
75
+ if ((error as NodeJS.ErrnoException)?.code === 'ENOENT') {
76
+ return undefined;
77
+ }
78
+ throw error;
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Reads the entry point from a manifest file.
84
+ *
85
+ * @param manifestPath - Absolute path to model.yaml
86
+ * @returns Entry file path (relative), defaults to 'index.dlang'
87
+ */
88
+ export async function readEntryFromManifest(manifestPath: string): Promise<string> {
89
+ try {
90
+ const manifest = await readManifest(manifestPath);
91
+ return manifest?.model?.entry ?? DEFAULT_ENTRY_FILE;
92
+ } catch {
93
+ return DEFAULT_ENTRY_FILE;
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Gets the absolute entry file path for a manifest.
99
+ *
100
+ * @param manifestPath - Absolute path to model.yaml
101
+ * @returns Absolute path to the entry file
102
+ */
103
+ export async function getEntryPath(manifestPath: string): Promise<string> {
104
+ const entry = await readEntryFromManifest(manifestPath);
105
+ return path.resolve(path.dirname(manifestPath), entry);
106
+ }
107
+
108
+ /**
109
+ * Discovers all manifest files within given directories.
110
+ * Only checks direct children, not recursive subdirectories.
111
+ *
112
+ * @param directories - Array of absolute directory paths to search
113
+ * @returns Array of manifest info objects
114
+ */
115
+ export async function findManifestsInDirectories(
116
+ directories: string[]
117
+ ): Promise<Array<{ manifestPath: string; entryPath: string }>> {
118
+ const results: Array<{ manifestPath: string; entryPath: string }> = [];
119
+
120
+ for (const dir of directories) {
121
+ const manifestPath = await findNearestManifest(dir);
122
+ if (manifestPath) {
123
+ const entryPath = await getEntryPath(manifestPath);
124
+ results.push({ manifestPath, entryPath });
125
+ }
126
+ }
127
+
128
+ return results;
129
+ }
130
+
131
+ /** Default manifest filename */
132
+ export { DEFAULT_MANIFEST_FILENAME, DEFAULT_ENTRY_FILE };
@@ -34,6 +34,7 @@ export const IssueCodes = {
34
34
  ImportMissingRef: 'import-missing-ref',
35
35
  ImportAbsolutePath: 'import-absolute-path',
36
36
  ImportEscapesWorkspace: 'import-escapes-workspace',
37
+ ImportUnresolved: 'import-unresolved',
37
38
 
38
39
  // Domain Issues
39
40
  DomainNoVision: 'domain-no-vision',
@@ -56,6 +57,9 @@ export const IssueCodes = {
56
57
  ContextMapNoRelationships: 'context-map-no-relationships',
57
58
  DomainMapNoDomains: 'domain-map-no-domains',
58
59
 
60
+ // Reference Issues
61
+ UnresolvedReference: 'unresolved-reference',
62
+
59
63
  // Metadata Issues
60
64
  MetadataMissingName: 'metadata-missing-name',
61
65
 
@@ -262,6 +266,14 @@ export const ValidationMessages = {
262
266
  `Local path dependency '${alias}' escapes workspace boundary.\n` +
263
267
  `Hint: Local dependencies must be within the workspace. Consider moving the dependency or using a git-based source.`,
264
268
 
269
+ /**
270
+ * Error when import path cannot be resolved to a file.
271
+ * @param uri - The import URI that couldn't be resolved
272
+ */
273
+ IMPORT_UNRESOLVED: (uri: string) =>
274
+ `Cannot resolve import '${uri}'.\n` +
275
+ `Hint: Check that the file exists and the path is correct.`,
276
+
265
277
  // ========================================================================
266
278
  // Context Map & Domain Map Validation
267
279
  // ========================================================================
@@ -291,6 +303,18 @@ export const ValidationMessages = {
291
303
  `Domain Map '${name}' contains no domains.\n` +
292
304
  `Hint: Use 'contains DomainA, DomainB' to specify which domains are in the map.`,
293
305
 
306
+ // ========================================================================
307
+ // Reference Resolution Validation
308
+ // ========================================================================
309
+
310
+ /**
311
+ * Error when a reference cannot be resolved (for MultiReferences).
312
+ * @param type - The type being referenced (e.g., 'BoundedContext')
313
+ * @param name - The unresolved name
314
+ */
315
+ UNRESOLVED_REFERENCE: (type: string, name: string) =>
316
+ `Could not resolve reference to ${type} named '${name}'.`,
317
+
294
318
  // ========================================================================
295
319
  // Metadata Validation
296
320
  // ========================================================================
@@ -1,11 +1,11 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- import type { ValidationAcceptor, ValidationChecks } from 'langium';
3
+ import type { ValidationAcceptor, ValidationChecks, LangiumDocument } from 'langium';
4
4
  import { Cancellation } from 'langium';
5
5
  import type { DomainLangAstType, ImportStatement } from '../generated/ast.js';
6
6
  import type { DomainLangServices } from '../domain-lang-module.js';
7
- import type { LangiumDocument } from 'langium';
8
7
  import type { WorkspaceManager } from '../services/workspace-manager.js';
8
+ import type { ImportResolver } from '../services/import-resolver.js';
9
9
  import type { ExtendedDependencySpec, ModelManifest, LockFile } from '../services/types.js';
10
10
  import { ValidationMessages, buildCodeDescription, IssueCodes } from './constants.js';
11
11
 
@@ -16,15 +16,18 @@ import { ValidationMessages, buildCodeDescription, IssueCodes } from './constant
16
16
  * the shared WorkspaceManager service with its cached manifest/lock file reading.
17
17
  *
18
18
  * Checks:
19
+ * - All import URIs resolve to existing files
19
20
  * - External imports require manifest + alias
20
21
  * - Local path dependencies stay inside workspace
21
22
  * - Lock file exists for external dependencies
22
23
  */
23
24
  export class ImportValidator {
24
25
  private readonly workspaceManager: WorkspaceManager;
26
+ private readonly importResolver: ImportResolver;
25
27
 
26
28
  constructor(services: DomainLangServices) {
27
29
  this.workspaceManager = services.imports.WorkspaceManager;
30
+ this.importResolver = services.imports.ImportResolver;
28
31
  }
29
32
 
30
33
  /**
@@ -49,6 +52,13 @@ export class ImportValidator {
49
52
  return;
50
53
  }
51
54
 
55
+ // First, verify the import resolves to a valid file
56
+ // This catches renamed/moved/deleted files immediately
57
+ const resolveError = await this.validateImportResolves(imp, document, accept);
58
+ if (resolveError) {
59
+ return; // Don't continue with other validations if can't resolve
60
+ }
61
+
52
62
  if (!this.isExternalImport(imp.uri)) {
53
63
  return;
54
64
  }
@@ -118,6 +128,63 @@ export class ImportValidator {
118
128
  return true;
119
129
  }
120
130
 
131
+ /**
132
+ * Validates that an import URI resolves to an existing file.
133
+ * Returns true if there was an error (import doesn't resolve).
134
+ */
135
+ private async validateImportResolves(
136
+ imp: ImportStatement,
137
+ document: LangiumDocument,
138
+ accept: ValidationAcceptor
139
+ ): Promise<boolean> {
140
+ if (!imp.uri) {
141
+ return true; // Error already reported
142
+ }
143
+
144
+ const docDir = path.dirname(document.uri.fsPath);
145
+
146
+ try {
147
+ const resolvedUri = await this.importResolver.resolveFrom(docDir, imp.uri);
148
+
149
+ // Check if the resolved file actually exists
150
+ const filePath = resolvedUri.fsPath;
151
+ const exists = await this.fileExists(filePath);
152
+
153
+ if (!exists) {
154
+ accept('error', ValidationMessages.IMPORT_UNRESOLVED(imp.uri), {
155
+ node: imp,
156
+ property: 'uri',
157
+ codeDescription: buildCodeDescription('language.md', 'imports'),
158
+ data: { code: IssueCodes.ImportUnresolved, uri: imp.uri }
159
+ });
160
+ return true;
161
+ }
162
+
163
+ return false;
164
+ } catch {
165
+ // Resolution failed - report as unresolved import
166
+ accept('error', ValidationMessages.IMPORT_UNRESOLVED(imp.uri), {
167
+ node: imp,
168
+ property: 'uri',
169
+ codeDescription: buildCodeDescription('language.md', 'imports'),
170
+ data: { code: IssueCodes.ImportUnresolved, uri: imp.uri }
171
+ });
172
+ return true;
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Checks if a file exists (async).
178
+ */
179
+ private async fileExists(filePath: string): Promise<boolean> {
180
+ try {
181
+ const stat = await fs.stat(filePath);
182
+ return stat.isFile();
183
+ } catch {
184
+ return false;
185
+ }
186
+ }
187
+
121
188
  /**
122
189
  * Gets the normalized dependency configuration for an alias.
123
190
  */
@@ -200,17 +267,23 @@ export class ImportValidator {
200
267
  return;
201
268
  }
202
269
 
203
- const workspaceRoot = this.workspaceManager.getWorkspaceRoot();
204
- const resolvedPath = path.resolve(workspaceRoot, dependencyPath);
205
- const relativeToWorkspace = path.relative(workspaceRoot, resolvedPath);
270
+ try {
271
+ const workspaceRoot = this.workspaceManager.getWorkspaceRoot();
272
+ const resolvedPath = path.resolve(workspaceRoot, dependencyPath);
273
+ const relativeToWorkspace = path.relative(workspaceRoot, resolvedPath);
206
274
 
207
- if (relativeToWorkspace.startsWith('..') || path.isAbsolute(relativeToWorkspace)) {
208
- accept('error', ValidationMessages.IMPORT_ESCAPES_WORKSPACE(alias), {
209
- node: imp,
210
- property: 'uri',
211
- codeDescription: buildCodeDescription('language.md', 'imports'),
212
- data: { code: IssueCodes.ImportEscapesWorkspace, alias }
213
- });
275
+ if (relativeToWorkspace.startsWith('..') || path.isAbsolute(relativeToWorkspace)) {
276
+ accept('error', ValidationMessages.IMPORT_ESCAPES_WORKSPACE(alias), {
277
+ node: imp,
278
+ property: 'uri',
279
+ codeDescription: buildCodeDescription('language.md', 'imports'),
280
+ data: { code: IssueCodes.ImportEscapesWorkspace, alias }
281
+ });
282
+ }
283
+ } catch (error) {
284
+ // WorkspaceManager not initialized - skip workspace boundary check
285
+ // This can happen for standalone files without model.yaml
286
+ console.warn(`Could not validate workspace boundary for path dependency: ${error}`);
214
287
  }
215
288
  }
216
289
 
@@ -238,17 +311,22 @@ export class ImportValidator {
238
311
  return;
239
312
  }
240
313
 
241
- const workspaceRoot = this.workspaceManager.getWorkspaceRoot();
242
- const cacheDir = this.getCacheDirectory(workspaceRoot, packageKey, lockedDep.commit);
314
+ try {
315
+ const workspaceRoot = this.workspaceManager.getWorkspaceRoot();
316
+ const cacheDir = this.getCacheDirectory(workspaceRoot, packageKey, lockedDep.commit);
243
317
 
244
- const cacheExists = await this.directoryExists(cacheDir);
245
- if (!cacheExists) {
246
- accept('error', ValidationMessages.IMPORT_NOT_INSTALLED(alias), {
247
- node: imp,
248
- property: 'uri',
249
- codeDescription: buildCodeDescription('language.md', 'imports'),
250
- data: { code: IssueCodes.ImportNotInstalled, alias }
251
- });
318
+ const cacheExists = await this.directoryExists(cacheDir);
319
+ if (!cacheExists) {
320
+ accept('error', ValidationMessages.IMPORT_NOT_INSTALLED(alias), {
321
+ node: imp,
322
+ property: 'uri',
323
+ codeDescription: buildCodeDescription('language.md', 'imports'),
324
+ data: { code: IssueCodes.ImportNotInstalled, alias }
325
+ });
326
+ }
327
+ } catch (error) {
328
+ // WorkspaceManager not initialized - log warning but continue
329
+ console.warn(`Could not validate cached package for ${alias}: ${error}`);
252
330
  }
253
331
  }
254
332
 
@@ -286,8 +364,13 @@ export function createImportChecks(services: DomainLangServices): ValidationChec
286
364
  return {
287
365
  // Langium 4.x supports async validators via MaybePromise<void>
288
366
  ImportStatement: async (imp, accept, cancelToken) => {
289
- const document = imp.$document;
290
- if (!document) return;
367
+ // Get document from root (Model), not from ImportStatement
368
+ // Langium sets $document only on the root AST node
369
+ const root = imp.$container;
370
+ const document = root?.$document;
371
+ if (!document) {
372
+ return;
373
+ }
291
374
 
292
375
  await validator.checkImportPath(imp, accept, document, cancelToken);
293
376
  }
@@ -1,6 +1,6 @@
1
1
  import type { ValidationAcceptor } from 'langium';
2
2
  import type { ContextMap, DomainMap } from '../generated/ast.js';
3
- import { ValidationMessages, buildCodeDescription } from './constants.js';
3
+ import { ValidationMessages, buildCodeDescription, IssueCodes } from './constants.js';
4
4
 
5
5
  /**
6
6
  * Validates that a context map contains at least one bounded context.
@@ -22,6 +22,36 @@ function validateContextMapHasContexts(
22
22
  }
23
23
  }
24
24
 
25
+ /**
26
+ * Validates that MultiReference items in a context map resolve.
27
+ * Langium doesn't report errors for unresolved MultiReference items by default,
28
+ * so we need custom validation to catch these cases.
29
+ *
30
+ * @param map - The context map to validate
31
+ * @param accept - The validation acceptor for reporting issues
32
+ */
33
+ function validateContextMapReferences(
34
+ map: ContextMap,
35
+ accept: ValidationAcceptor
36
+ ): void {
37
+ if (!map.boundedContexts) return;
38
+
39
+ for (const multiRef of map.boundedContexts) {
40
+ // A MultiReference has a $refText (the source text) and items (resolved refs)
41
+ // If $refText exists but items is empty, the reference didn't resolve
42
+ const refText = multiRef.$refText;
43
+ if (refText && multiRef.items.length === 0) {
44
+ accept('error', ValidationMessages.UNRESOLVED_REFERENCE('BoundedContext', refText), {
45
+ node: map,
46
+ // Find the CST node for this specific reference
47
+ property: 'boundedContexts',
48
+ index: map.boundedContexts.indexOf(multiRef),
49
+ code: IssueCodes.UnresolvedReference
50
+ });
51
+ }
52
+ }
53
+ }
54
+
25
55
  /**
26
56
  * Validates that a context map has at least one relationship if it contains multiple contexts.
27
57
  * Multiple unrelated contexts should have documented relationships.
@@ -66,11 +96,38 @@ function validateDomainMapHasDomains(
66
96
  }
67
97
  }
68
98
 
99
+ /**
100
+ * Validates that MultiReference items in a domain map resolve.
101
+ *
102
+ * @param map - The domain map to validate
103
+ * @param accept - The validation acceptor for reporting issues
104
+ */
105
+ function validateDomainMapReferences(
106
+ map: DomainMap,
107
+ accept: ValidationAcceptor
108
+ ): void {
109
+ if (!map.domains) return;
110
+
111
+ for (const multiRef of map.domains) {
112
+ const refText = multiRef.$refText;
113
+ if (refText && multiRef.items.length === 0) {
114
+ accept('error', ValidationMessages.UNRESOLVED_REFERENCE('Domain', refText), {
115
+ node: map,
116
+ property: 'domains',
117
+ index: map.domains.indexOf(multiRef),
118
+ code: IssueCodes.UnresolvedReference
119
+ });
120
+ }
121
+ }
122
+ }
123
+
69
124
  export const contextMapChecks = [
70
125
  validateContextMapHasContexts,
126
+ validateContextMapReferences,
71
127
  validateContextMapHasRelationships
72
128
  ];
73
129
 
74
130
  export const domainMapChecks = [
75
- validateDomainMapHasDomains
131
+ validateDomainMapHasDomains,
132
+ validateDomainMapReferences
76
133
  ];
@@ -1,50 +0,0 @@
1
- /**
2
- * DDD Pattern Explanations for Hover Documentation
3
- *
4
- * Provides plain-English explanations for DDD integration patterns,
5
- * relationship types, and decision categories.
6
- */
7
- /**
8
- * Explanations for DDD integration role patterns (e.g., PL, ACL, SK).
9
- */
10
- export declare const rolePatternExplanations: Record<string, string>;
11
- /**
12
- * Explanations for relationship types (e.g., Partnership, CustomerSupplier).
13
- */
14
- export declare const relationshipTypeExplanations: Record<string, string>;
15
- /**
16
- * Explanations for relationship arrows.
17
- */
18
- export declare const arrowExplanations: Record<string, string>;
19
- /**
20
- * Explanations for decision categories.
21
- */
22
- export declare const decisionCategoryExplanations: Record<string, string>;
23
- /**
24
- * Explanations for common DDD classifications.
25
- */
26
- export declare const classificationExplanations: Record<string, string>;
27
- /**
28
- * Get explanation for a role pattern.
29
- */
30
- export declare function explainRolePattern(role: string): string | undefined;
31
- /**
32
- * Get explanation for a relationship type.
33
- */
34
- export declare function explainRelationshipType(type: string): string | undefined;
35
- /**
36
- * Get explanation for an arrow symbol.
37
- */
38
- export declare function explainArrow(arrow: string): string | undefined;
39
- /**
40
- * Get explanation for a decision category.
41
- */
42
- export declare function explainDecisionCategory(category: string): string | undefined;
43
- /**
44
- * Get explanation for a classification.
45
- */
46
- export declare function explainClassification(name: string): string | undefined;
47
- /**
48
- * Generate relationship explanation from roles and type.
49
- */
50
- export declare function generateRelationshipExplanation(leftRoles: string[] | undefined, arrow: string | undefined, rightRoles: string[] | undefined, type: string | undefined): string;