@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,152 @@
1
+ import path from 'node:path';
2
+ import { URI, type LangiumDocument, type LangiumDocuments } from 'langium';
3
+ import type { Model } from '../generated/ast.js';
4
+ import type { GitUrlResolver } from '../services/git-url-resolver.js';
5
+ import { WorkspaceManager } from '../services/workspace-manager.js';
6
+ import { ImportResolver } from '../services/import-resolver.js';
7
+ import type { DomainLangServices } from '../domain-lang-module.js';
8
+
9
+ /**
10
+ * Lazily initialized workspace manager for standalone (non-LSP) usage.
11
+ * Used by import graph building when services aren't available from DI.
12
+ */
13
+ let standaloneWorkspaceManager: WorkspaceManager | undefined;
14
+ let standaloneImportResolver: ImportResolver | undefined;
15
+ let lastInitializedDir: string | undefined;
16
+
17
+ /**
18
+ * Gets or creates a standalone import resolver for non-LSP contexts.
19
+ * Creates its own WorkspaceManager if not previously initialized for this directory.
20
+ *
21
+ * NOTE: In LSP contexts, prefer using services.imports.ImportResolver directly.
22
+ * This function exists for utilities that don't have access to the service container.
23
+ *
24
+ * @param startDir - Directory to start workspace search from
25
+ * @returns Promise resolving to the import resolver
26
+ */
27
+ async function getStandaloneImportResolver(startDir: string): Promise<ImportResolver> {
28
+ // Re-initialize if directory changed (workspace boundary)
29
+ if (lastInitializedDir !== startDir || !standaloneImportResolver) {
30
+ standaloneWorkspaceManager = new WorkspaceManager({ autoResolve: false, allowNetwork: false });
31
+ try {
32
+ await standaloneWorkspaceManager.initialize(startDir);
33
+ } catch (error) {
34
+ console.warn(`Failed to initialize workspace: ${error instanceof Error ? error.message : String(error)}`);
35
+ }
36
+ const services = {
37
+ imports: { WorkspaceManager: standaloneWorkspaceManager }
38
+ } as DomainLangServices;
39
+ standaloneImportResolver = new ImportResolver(services);
40
+ lastInitializedDir = startDir;
41
+ }
42
+ return standaloneImportResolver;
43
+ }
44
+
45
+ /**
46
+ * Gets the git URL resolver from a workspace manager.
47
+ *
48
+ * @param startDir - Directory to start workspace search from
49
+ * @returns Promise resolving to the git URL resolver
50
+ */
51
+ async function getGitResolver(startDir: string): Promise<GitUrlResolver> {
52
+ const workspaceManager = new WorkspaceManager({ autoResolve: false, allowNetwork: false });
53
+ await workspaceManager.initialize(startDir);
54
+ return workspaceManager.getGitResolver();
55
+ }
56
+
57
+ /**
58
+ * Resolves an import path to an absolute file URI.
59
+ *
60
+ * Delegates to ImportResolver which implements PRS-010 semantics:
61
+ * - File imports (with .dlang extension): Direct file access
62
+ * - Module imports (no extension): Requires model.yaml in directory
63
+ * - External dependencies: Resolved via manifest and lock file
64
+ *
65
+ * @param importingDoc - The document containing the import statement
66
+ * @param rawImportPath - The raw import path from the import statement
67
+ * @returns Resolved URI to the imported file
68
+ * @throws {Error} If the import cannot be resolved
69
+ */
70
+ export async function resolveImportPath(
71
+ importingDoc: LangiumDocument,
72
+ rawImportPath: string
73
+ ): Promise<URI> {
74
+ const baseDir = path.dirname(importingDoc.uri.fsPath);
75
+ const resolver = await getStandaloneImportResolver(baseDir);
76
+ return resolver.resolveFrom(baseDir, rawImportPath);
77
+ }
78
+
79
+ /**
80
+ * Ensures the import graph is loaded from an entry file.
81
+ *
82
+ * @param entryFilePath - Absolute or workspace-relative path to entry file
83
+ * @param langiumDocuments - The Langium documents manager
84
+ * @returns Set of URIs (as strings) for all documents in the import graph
85
+ * @throws {Error} If entry file cannot be resolved or loaded
86
+ */
87
+ export async function ensureImportGraphFromEntryFile(
88
+ entryFilePath: string,
89
+ langiumDocuments: LangiumDocuments
90
+ ): Promise<Set<string>> {
91
+ const entryUri = URI.file(path.resolve(entryFilePath));
92
+ const entryDoc = await langiumDocuments.getOrCreateDocument(entryUri);
93
+ return ensureImportGraphFromDocument(entryDoc, langiumDocuments);
94
+ }
95
+
96
+ /**
97
+ * Recursively builds the import graph from a document.
98
+ *
99
+ * @param document - The starting document
100
+ * @param langiumDocuments - The Langium documents manager
101
+ * @returns Set of URIs (as strings) for all documents in the import graph
102
+ */
103
+ export async function ensureImportGraphFromDocument(
104
+ document: LangiumDocument,
105
+ langiumDocuments: LangiumDocuments
106
+ ): Promise<Set<string>> {
107
+ const visited = new Set<string>();
108
+
109
+ async function visit(doc: LangiumDocument): Promise<void> {
110
+ const uriString = doc.uri.toString();
111
+ if (visited.has(uriString)) return;
112
+ visited.add(uriString);
113
+
114
+ const model = doc.parseResult.value as unknown as Model;
115
+ for (const imp of model.imports ?? []) {
116
+ if (!imp.uri) continue;
117
+
118
+ // Use new resolveImportPath that supports git URLs
119
+ const resolvedUri = await resolveImportPath(doc, imp.uri);
120
+ const childDoc = await langiumDocuments.getOrCreateDocument(resolvedUri);
121
+ await visit(childDoc);
122
+ }
123
+ }
124
+
125
+ await visit(document);
126
+ return visited;
127
+ }
128
+
129
+ /**
130
+ * Gets cache statistics for git imports.
131
+ *
132
+ * @returns Cache statistics including size and number of cached repositories
133
+ */
134
+ export async function getGitCacheStats(startDir: string = process.cwd()): Promise<{
135
+ totalSize: number;
136
+ repoCount: number;
137
+ cacheDir: string;
138
+ }> {
139
+ const resolver = await getGitResolver(startDir);
140
+ return await resolver.getCacheStats();
141
+ }
142
+
143
+ /**
144
+ * Clears the git import cache.
145
+ *
146
+ * @param startDir - Starting directory for workspace resolution
147
+ * @returns Promise that resolves when cache is cleared
148
+ */
149
+ export async function clearGitCache(startDir: string = process.cwd()): Promise<void> {
150
+ const resolver = await getGitResolver(startDir);
151
+ return await resolver.clearCache();
152
+ }
@@ -0,0 +1,99 @@
1
+ import type { ValidationAcceptor } from 'langium';
2
+ import type { BoundedContext } from '../generated/ast.js';
3
+ import { ValidationMessages, buildCodeDescription } from './constants.js';
4
+
5
+ /**
6
+ * Validates that a bounded context has a description.
7
+ *
8
+ * @param bc - The bounded context to validate
9
+ * @param accept - The validation acceptor for reporting issues
10
+ */
11
+ function validateBoundedContextHasDescription(
12
+ bc: BoundedContext,
13
+ accept: ValidationAcceptor
14
+ ): void {
15
+ if (!bc.description) {
16
+ accept('warning', ValidationMessages.BOUNDED_CONTEXT_NO_DESCRIPTION(bc.name), {
17
+ node: bc,
18
+ keyword: 'BoundedContext',
19
+ codeDescription: buildCodeDescription('language.md', 'bounded-contexts')
20
+ });
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Validates that a bounded context belongs to a domain.
26
+ * A BoundedContext must have a 'for Domain' clause.
27
+ *
28
+ * @param bc - The bounded context to validate
29
+ * @param accept - The validation acceptor for reporting issues
30
+ */
31
+ function validateBoundedContextHasDomain(
32
+ bc: BoundedContext,
33
+ accept: ValidationAcceptor
34
+ ): void {
35
+ if (!bc.domain) {
36
+ accept('warning', ValidationMessages.BOUNDED_CONTEXT_NO_DOMAIN(bc.name), {
37
+ node: bc,
38
+ keyword: 'for',
39
+ codeDescription: buildCodeDescription('language.md', 'bounded-contexts')
40
+ });
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Validates conflicts between inline and block classification assignment.
46
+ * Warns when both inline ('as') and block ('classification:') are used.
47
+ * Inline values take precedence per PRS-008.
48
+ *
49
+ * FR-9.2: Inline/Block Conflict Validation
50
+ */
51
+ function validateBoundedContextClassificationConflict(
52
+ bc: BoundedContext,
53
+ accept: ValidationAcceptor
54
+ ): void {
55
+ if (bc.classification.length > 1) {
56
+ const inlineClassificationName = bc.classification[0].ref?.name;
57
+ const blockClassificationName = bc.classification[1].ref?.name;
58
+
59
+ // Warn if defined multiple times
60
+ accept('warning', ValidationMessages.BOUNDED_CONTEXT_CLASSIFICATION_CONFLICT(bc.name, inlineClassificationName, blockClassificationName), {
61
+ node: bc,
62
+ property: 'classification',
63
+ index: 1,
64
+ codeDescription: buildCodeDescription('language.md', 'bounded-contexts')
65
+ });
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Validates conflicts between inline and block team assignment.
71
+ * Warns when both inline ('by') and block ('team:') are used.
72
+ * Inline values take precedence per PRS-008.
73
+ *
74
+ * FR-2.3: Inline/Block Conflict Validation
75
+ */
76
+ function validateBoundedContextTeamConflict(
77
+ bc: BoundedContext,
78
+ accept: ValidationAcceptor
79
+ ): void {
80
+ if (bc.team.length > 1) {
81
+ const inlineTeamName = bc.team[0].ref?.name;
82
+ const blockTeamName = bc.team[1].ref?.name;
83
+
84
+ // Warn if defined multiple times
85
+ accept('warning', ValidationMessages.BOUNDED_CONTEXT_TEAM_CONFLICT(bc.name, inlineTeamName, blockTeamName), {
86
+ node: bc,
87
+ property: 'team',
88
+ index: 1,
89
+ codeDescription: buildCodeDescription('language.md', 'bounded-contexts')
90
+ });
91
+ }
92
+ }
93
+
94
+ export const boundedContextChecks = [
95
+ validateBoundedContextHasDescription,
96
+ validateBoundedContextHasDomain,
97
+ validateBoundedContextClassificationConflict,
98
+ validateBoundedContextTeamConflict
99
+ ];
@@ -0,0 +1,5 @@
1
+ import type { ValidationCheck } from 'langium';
2
+ import type { Classification } from '../generated/ast.js';
3
+
4
+ // No validation checks needed for classifications currently
5
+ export const classificationChecks: ValidationCheck<Classification>[] = [];
@@ -0,0 +1,304 @@
1
+ /**
2
+ * Validation message constants for DomainLang.
3
+ *
4
+ * Centralizes all validation messages to ensure consistency
5
+ * and facilitate internationalization in the future.
6
+ *
7
+ * Messages follow VS Code conventions:
8
+ * - Clear problem statement
9
+ * - Brief DDD context explaining why it matters
10
+ * - Inline example showing the fix
11
+ * - Clickable documentation link via CodeDescription
12
+ */
13
+
14
+ import type { CodeDescription } from 'vscode-languageserver-types';
15
+
16
+ // ============================================================================
17
+ // Issue Codes for Code Actions
18
+ // ============================================================================
19
+
20
+ /**
21
+ * Diagnostic codes used to identify validation issues.
22
+ * Code actions match on these codes to provide quick fixes.
23
+ *
24
+ * Naming convention: CATEGORY_SPECIFIC_ISSUE
25
+ */
26
+ export const IssueCodes = {
27
+ // Import & Dependency Issues
28
+ ImportMissingUri: 'import-missing-uri',
29
+ ImportRequiresManifest: 'import-requires-manifest',
30
+ ImportNotInManifest: 'import-not-in-manifest',
31
+ ImportNotInstalled: 'import-not-installed',
32
+ ImportConflictingSourcePath: 'import-conflicting-source-path',
33
+ ImportMissingSourceOrPath: 'import-missing-source-or-path',
34
+ ImportMissingRef: 'import-missing-ref',
35
+ ImportAbsolutePath: 'import-absolute-path',
36
+ ImportEscapesWorkspace: 'import-escapes-workspace',
37
+
38
+ // Domain Issues
39
+ DomainNoVision: 'domain-no-vision',
40
+ DomainCircularHierarchy: 'domain-circular-hierarchy',
41
+
42
+ // Bounded Context Issues
43
+ BoundedContextNoDescription: 'bounded-context-no-description',
44
+ BoundedContextNoDomain: 'bounded-context-no-domain',
45
+ BoundedContextClassificationConflict: 'bounded-context-classification-conflict',
46
+ BoundedContextTeamConflict: 'bounded-context-team-conflict',
47
+
48
+ // Integration Pattern Issues
49
+ SharedKernelNotBidirectional: 'shared-kernel-not-bidirectional',
50
+ AclOnWrongSide: 'acl-on-wrong-side',
51
+ ConformistOnWrongSide: 'conformist-on-wrong-side',
52
+ TooManyPatterns: 'too-many-patterns',
53
+
54
+ // Context/Domain Map Issues
55
+ ContextMapNoContexts: 'context-map-no-contexts',
56
+ ContextMapNoRelationships: 'context-map-no-relationships',
57
+ DomainMapNoDomains: 'domain-map-no-domains',
58
+
59
+ // Metadata Issues
60
+ MetadataMissingName: 'metadata-missing-name',
61
+
62
+ // General Issues
63
+ DuplicateElement: 'duplicate-element'
64
+ } as const;
65
+
66
+ export type IssueCode = typeof IssueCodes[keyof typeof IssueCodes];
67
+
68
+ // ============================================================================
69
+ // Documentation Link Utilities
70
+ // ============================================================================
71
+
72
+ const REPO_BASE = 'https://github.com/larsbaunwall/DomainLang/blob/main';
73
+ const DOCS_BASE = `${REPO_BASE}/dsl/domain-lang/docs`;
74
+
75
+ /**
76
+ * Builds a documentation URL for error messages.
77
+ * @param docPath - Relative path from docs/ folder
78
+ * @param anchor - Optional section anchor (without #)
79
+ */
80
+ const buildDocLink = (docPath: string, anchor?: string): string =>
81
+ `${DOCS_BASE}/${docPath}${anchor ? `#${anchor}` : ''}`;
82
+
83
+ /**
84
+ * Creates a CodeDescription for clickable documentation links in VS Code.
85
+ * @param docPath - Relative path from docs/ folder
86
+ * @param anchor - Optional section anchor (without #)
87
+ */
88
+ export const buildCodeDescription = (docPath: string, anchor?: string): CodeDescription => ({
89
+ href: buildDocLink(docPath, anchor)
90
+ });
91
+
92
+ // ============================================================================
93
+ // Enhanced Validation Messages
94
+ // ============================================================================
95
+
96
+ export const ValidationMessages = {
97
+ /**
98
+ * Warning message when a domain lacks a vision statement.
99
+ * @param name - The name of the domain
100
+ */
101
+ DOMAIN_NO_VISION: (name: string) =>
102
+ `Domain '${name}' is missing a vision statement.`,
103
+
104
+ /**
105
+ * Error message when a circular domain hierarchy is detected.
106
+ * @param cycle - Array of domain names forming the cycle
107
+ */
108
+ DOMAIN_CIRCULAR_HIERARCHY: (cycle: string[]) =>
109
+ `Circular domain hierarchy detected: ${cycle.join(' → ')}.`,
110
+
111
+ /**
112
+ * Warning message when a bounded context lacks a description.
113
+ * @param name - The name of the bounded context
114
+ */
115
+ BOUNDED_CONTEXT_NO_DESCRIPTION: (name: string) =>
116
+ `Bounded Context '${name}' is missing a description.`,
117
+
118
+ /**
119
+ * Warning message when a bounded context lacks a domain reference.
120
+ * @param name - The name of the bounded context
121
+ */
122
+ BOUNDED_CONTEXT_NO_DOMAIN: (name: string) =>
123
+ `Bounded Context '${name}' must belong to a domain. Use 'for DomainName'.`,
124
+
125
+ /**
126
+ * Warning when classification is specified both inline and in a block.
127
+ * Inline value takes precedence.
128
+ * @param bcName - The name of the bounded context
129
+ * @param inlineClassification - The inline classification name (from 'as')
130
+ * @param blockClassification - The block classification name (from 'classification:')
131
+ */
132
+ BOUNDED_CONTEXT_CLASSIFICATION_CONFLICT: (bcName: string, inlineClassification?: string, blockClassification?: string) =>
133
+ `Classification specified both inline${inlineClassification ? ` ('as ${inlineClassification}')` : ''} and in block${blockClassification ? ` ('classification: ${blockClassification}')` : ''}. Inline value takes precedence.`,
134
+
135
+ /**
136
+ * Warning when team is specified both inline and in a block.
137
+ * Inline value takes precedence.
138
+ * @param bcName - The name of the bounded context
139
+ * @param inlineTeam - The inline team name (from 'by')
140
+ * @param blockTeam - The block team name (from 'team:')
141
+ */
142
+ BOUNDED_CONTEXT_TEAM_CONFLICT: (bcName: string, inlineTeam?: string, blockTeam?: string) =>
143
+ `Team specified both inline${inlineTeam ? ` ('by ${inlineTeam}')` : ''} and in block${blockTeam ? ` ('team: ${blockTeam}')` : ''}. Inline value takes precedence.`,
144
+
145
+ /**
146
+ * Error message when an element is defined multiple times.
147
+ * @param fqn - The fully qualified name of the duplicate element
148
+ */
149
+ DUPLICATE_ELEMENT: (fqn: string) =>
150
+ `Duplicate element: '${fqn}' is already defined.`,
151
+
152
+ // ========================================================================
153
+ // Integration Pattern & Relationship Validation
154
+ // ========================================================================
155
+
156
+ /**
157
+ * Warning when SharedKernel pattern uses incorrect arrow direction.
158
+ * SharedKernel requires bidirectional relationship.
159
+ */
160
+ SHARED_KERNEL_MUST_BE_BIDIRECTIONAL: (leftContext: string, rightContext: string, arrow: string) =>
161
+ `SharedKernel between '${leftContext}' and '${rightContext}' requires bidirectional arrow '<->', not '${arrow}'.`,
162
+
163
+ /**
164
+ * Warning when Anti-Corruption Layer is on the wrong side of relationship.
165
+ * ACL should protect the consuming context (downstream).
166
+ */
167
+ ACL_ON_WRONG_SIDE: (context: string, side: 'left' | 'right') =>
168
+ `Anti-Corruption Layer (ACL) on '${context}' should be on downstream (consuming) side, not ${side} side.`,
169
+
170
+ /**
171
+ * Warning when Conformist pattern is on the wrong side.
172
+ * Conformist accepts upstream model without translation.
173
+ */
174
+ CONFORMIST_ON_WRONG_SIDE: (context: string, side: 'left' | 'right') =>
175
+ `Conformist (CF) on '${context}' should be on downstream (consuming) side, not ${side} side.`,
176
+
177
+ /**
178
+ * Info message when relationship has too many integration patterns.
179
+ * Suggests possible syntax confusion.
180
+ */
181
+ TOO_MANY_PATTERNS: (count: number, side: 'left' | 'right') =>
182
+ `Too many integration patterns (${count}) on ${side} side. Typically use 1-2 patterns per side.`,
183
+
184
+ // ========================================================================
185
+ // Import & Dependency Validation (PRS-010 Phase 8)
186
+ // ========================================================================
187
+
188
+ /**
189
+ * Error when import statement has no URI.
190
+ */
191
+ IMPORT_MISSING_URI: () =>
192
+ `Import statement must have a URI. Use: import "package" or import "./local-path"`,
193
+
194
+ /**
195
+ * Error when external dependency requires model.yaml but none exists.
196
+ * @param specifier - The import specifier (e.g., "core", "domainlang/patterns")
197
+ */
198
+ IMPORT_REQUIRES_MANIFEST: (specifier: string) =>
199
+ `External dependency '${specifier}' requires model.yaml.\n` +
200
+ `Hint: Create model.yaml and add the dependency:\n` +
201
+ ` dependencies:\n` +
202
+ ` ${specifier}:\n` +
203
+ ` ref: v1.0.0`,
204
+
205
+ /**
206
+ * Error when import specifier not found in manifest dependencies.
207
+ * @param alias - The dependency alias/specifier
208
+ */
209
+ IMPORT_NOT_IN_MANIFEST: (alias: string) =>
210
+ `Import '${alias}' not found in model.yaml dependencies.\n` +
211
+ `Hint: Run 'dlang add ${alias} <source>@<ref>' to add it, or manually add to model.yaml:\n` +
212
+ ` dependencies:\n` +
213
+ ` ${alias}:\n` +
214
+ ` ref: v1.0.0`,
215
+
216
+ /**
217
+ * Error when dependency not installed (no lock file entry).
218
+ * @param alias - The dependency alias
219
+ */
220
+ IMPORT_NOT_INSTALLED: (alias: string) =>
221
+ `Dependency '${alias}' not installed.\n` +
222
+ `Hint: Run 'dlang install' to fetch dependencies and generate model.lock.`,
223
+
224
+ /**
225
+ * Error when dependency has conflicting source and path definitions.
226
+ * @param alias - The dependency alias
227
+ */
228
+ IMPORT_CONFLICTING_SOURCE_PATH: (alias: string) =>
229
+ `Dependency '${alias}' cannot define both 'source' and 'path' in model.yaml.\n` +
230
+ `Hint: Use 'source' for git-based packages or 'path' for local workspace packages.`,
231
+
232
+ /**
233
+ * Error when dependency is missing both source and path.
234
+ * @param alias - The dependency alias
235
+ */
236
+ IMPORT_MISSING_SOURCE_OR_PATH: (alias: string) =>
237
+ `Dependency '${alias}' must define either 'source' or 'path' in model.yaml.\n` +
238
+ `Hint: Add 'source: owner/repo' for git packages, or 'path: ./local/path' for local packages.`,
239
+
240
+ /**
241
+ * Error when git dependency is missing ref (tag, branch, or commit).
242
+ * @param alias - The dependency alias
243
+ */
244
+ IMPORT_MISSING_REF: (alias: string) =>
245
+ `Dependency '${alias}' must specify a git ref in model.yaml.\n` +
246
+ `Hint: Add a git ref: 'ref: v1.0.0' (tag), 'ref: main' (branch), or a commit SHA.`,
247
+
248
+ /**
249
+ * Error when local path uses absolute path.
250
+ * @param alias - The dependency alias
251
+ * @param absolutePath - The absolute path that was specified
252
+ */
253
+ IMPORT_ABSOLUTE_PATH: (alias: string, absolutePath: string) =>
254
+ `Local path dependency '${alias}' cannot use absolute path '${absolutePath}'.\n` +
255
+ `Hint: Use a relative path from the workspace root, e.g., 'path: ./packages/shared'.`,
256
+
257
+ /**
258
+ * Error when local path escapes workspace boundary.
259
+ * @param alias - The dependency alias
260
+ */
261
+ IMPORT_ESCAPES_WORKSPACE: (alias: string) =>
262
+ `Local path dependency '${alias}' escapes workspace boundary.\n` +
263
+ `Hint: Local dependencies must be within the workspace. Consider moving the dependency or using a git-based source.`,
264
+
265
+ // ========================================================================
266
+ // Context Map & Domain Map Validation
267
+ // ========================================================================
268
+
269
+ /**
270
+ * Warning when context map contains no bounded contexts.
271
+ * @param name - The context map name
272
+ */
273
+ CONTEXT_MAP_NO_CONTEXTS: (name: string) =>
274
+ `Context Map '${name}' contains no bounded contexts.\n` +
275
+ `Hint: Use 'contains ContextA, ContextB' to specify which contexts are in the map.`,
276
+
277
+ /**
278
+ * Info when context map has multiple contexts but no relationships.
279
+ * @param name - The context map name
280
+ * @param count - Number of contexts
281
+ */
282
+ CONTEXT_MAP_NO_RELATIONSHIPS: (name: string, count: number) =>
283
+ `Context Map '${name}' contains ${count} contexts but no documented relationships.\n` +
284
+ `Hint: Add relationships to show how contexts integrate (e.g., '[OHS] A -> [CF] B').`,
285
+
286
+ /**
287
+ * Warning when domain map contains no domains.
288
+ * @param name - The domain map name
289
+ */
290
+ DOMAIN_MAP_NO_DOMAINS: (name: string) =>
291
+ `Domain Map '${name}' contains no domains.\n` +
292
+ `Hint: Use 'contains DomainA, DomainB' to specify which domains are in the map.`,
293
+
294
+ // ========================================================================
295
+ // Metadata Validation
296
+ // ========================================================================
297
+
298
+ /**
299
+ * Error when metadata is missing a name.
300
+ */
301
+ METADATA_MISSING_NAME: () =>
302
+ `Metadata must have a name.\n` +
303
+ `Hint: Define metadata with: Metadata MyMetadata { ... }`
304
+ } as const;
@@ -0,0 +1,33 @@
1
+ import { modelChecks } from './model.js';
2
+ import { domainChecks } from './domain.js';
3
+ import { boundedContextChecks } from './bounded-context.js';
4
+ import { classificationChecks } from './classification.js';
5
+ import { metadataChecks } from './metadata.js';
6
+ import { contextMapChecks, domainMapChecks } from './maps.js';
7
+ import { relationshipChecks } from './relationships.js';
8
+ import { createImportChecks } from './import.js';
9
+ import type { ValidationChecks } from 'langium';
10
+ import type { DomainLangAstType } from '../generated/ast.js';
11
+ import { DomainLangServices } from '../domain-lang-module.js';
12
+
13
+ export function registerValidationChecks(services: DomainLangServices): void {
14
+ const registry = services.validation.ValidationRegistry;
15
+
16
+ // Get import checks
17
+ const importChecks = createImportChecks(services);
18
+
19
+ // Compose the pipeline for each type
20
+ const pipeline: ValidationChecks<DomainLangAstType> = {
21
+ Model: modelChecks,
22
+ Domain: domainChecks,
23
+ BoundedContext: boundedContextChecks,
24
+ Classification: classificationChecks,
25
+ ContextMap: contextMapChecks,
26
+ DomainMap: domainMapChecks,
27
+ Metadata: metadataChecks.Metadata,
28
+ ImportStatement: importChecks.ImportStatement,
29
+ Relationship: relationshipChecks,
30
+ };
31
+
32
+ registry.register(pipeline);
33
+ }
@@ -0,0 +1,77 @@
1
+ import type { ValidationAcceptor } from 'langium';
2
+ import type { Domain } from '../generated/ast.js';
3
+ import { ValidationMessages, buildCodeDescription } from './constants.js';
4
+
5
+ /**
6
+ * Validates that a domain has a vision statement.
7
+ *
8
+ * @param domain - The domain to validate
9
+ * @param accept - The validation acceptor for reporting issues
10
+ */
11
+ function validateDomainHasVision(
12
+ domain: Domain,
13
+ accept: ValidationAcceptor
14
+ ): void {
15
+ if (!domain.vision) {
16
+ accept('warning', ValidationMessages.DOMAIN_NO_VISION(domain.name), {
17
+ node: domain,
18
+ keyword: 'Domain',
19
+ codeDescription: buildCodeDescription('language.md', 'domain-vision')
20
+ });
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Validates that a domain hierarchy does not contain circular references.
26
+ *
27
+ * The `Domain A in B` syntax expresses subdomain containment.
28
+ * Circular containment (A in B, B in C, C in A) is semantically invalid
29
+ * because it violates the fundamental concept of domain decomposition.
30
+ *
31
+ * @param domain - The domain to validate
32
+ * @param accept - The validation acceptor for reporting issues
33
+ */
34
+ function validateNoCyclicDomainHierarchy(
35
+ domain: Domain,
36
+ accept: ValidationAcceptor
37
+ ): void {
38
+ // Only check if this domain has a parent
39
+ if (!domain.parent?.ref) {
40
+ return;
41
+ }
42
+
43
+ const visited = new Set<Domain>();
44
+ const path: string[] = [domain.name];
45
+ let current: Domain | undefined = domain.parent.ref;
46
+
47
+ while (current) {
48
+ // Check if we've encountered this domain before (cycle detected)
49
+ if (visited.has(current)) {
50
+ // We found a cycle - report it
51
+ path.push(current.name);
52
+ accept('error', ValidationMessages.DOMAIN_CIRCULAR_HIERARCHY(path), {
53
+ node: domain,
54
+ property: 'parent',
55
+ codeDescription: buildCodeDescription('language.md', 'domain-hierarchy')
56
+ });
57
+ return;
58
+ }
59
+
60
+ // Check if we've looped back to the starting domain
61
+ if (current === domain) {
62
+ path.push(domain.name);
63
+ accept('error', ValidationMessages.DOMAIN_CIRCULAR_HIERARCHY(path), {
64
+ node: domain,
65
+ property: 'parent',
66
+ codeDescription: buildCodeDescription('language.md', 'domain-hierarchy')
67
+ });
68
+ return;
69
+ }
70
+
71
+ visited.add(current);
72
+ path.push(current.name);
73
+ current = current.parent?.ref;
74
+ }
75
+ }
76
+
77
+ export const domainChecks = [validateDomainHasVision, validateNoCyclicDomainHierarchy];