@domainlang/language 0.6.0 → 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 (35) hide show
  1. package/README.md +1 -1
  2. package/out/domain-lang-module.js +2 -0
  3. package/out/domain-lang-module.js.map +1 -1
  4. package/out/lsp/domain-lang-index-manager.d.ts +69 -1
  5. package/out/lsp/domain-lang-index-manager.js +173 -5
  6. package/out/lsp/domain-lang-index-manager.js.map +1 -1
  7. package/out/lsp/domain-lang-scope-provider.d.ts +67 -0
  8. package/out/lsp/domain-lang-scope-provider.js +95 -0
  9. package/out/lsp/domain-lang-scope-provider.js.map +1 -0
  10. package/out/lsp/domain-lang-workspace-manager.d.ts +25 -0
  11. package/out/lsp/domain-lang-workspace-manager.js +102 -3
  12. package/out/lsp/domain-lang-workspace-manager.js.map +1 -1
  13. package/out/main.js +114 -19
  14. package/out/main.js.map +1 -1
  15. package/out/services/import-resolver.d.ts +29 -6
  16. package/out/services/import-resolver.js +48 -9
  17. package/out/services/import-resolver.js.map +1 -1
  18. package/out/validation/constants.d.ts +13 -0
  19. package/out/validation/constants.js +18 -0
  20. package/out/validation/constants.js.map +1 -1
  21. package/out/validation/import.d.ts +11 -0
  22. package/out/validation/import.js +62 -2
  23. package/out/validation/import.js.map +1 -1
  24. package/out/validation/maps.js +51 -2
  25. package/out/validation/maps.js.map +1 -1
  26. package/package.json +1 -1
  27. package/src/domain-lang-module.ts +2 -0
  28. package/src/lsp/domain-lang-index-manager.ts +196 -5
  29. package/src/lsp/domain-lang-scope-provider.ts +134 -0
  30. package/src/lsp/domain-lang-workspace-manager.ts +128 -3
  31. package/src/main.ts +153 -22
  32. package/src/services/import-resolver.ts +60 -9
  33. package/src/validation/constants.ts +24 -0
  34. package/src/validation/import.ts +75 -2
  35. package/src/validation/maps.ts +59 -2
@@ -5,6 +5,7 @@ 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
7
  import type { WorkspaceManager } from '../services/workspace-manager.js';
8
+ import type { ImportResolver } from '../services/import-resolver.js';
8
9
  import type { ExtendedDependencySpec, ModelManifest, LockFile } from '../services/types.js';
9
10
  import { ValidationMessages, buildCodeDescription, IssueCodes } from './constants.js';
10
11
 
@@ -15,15 +16,18 @@ import { ValidationMessages, buildCodeDescription, IssueCodes } from './constant
15
16
  * the shared WorkspaceManager service with its cached manifest/lock file reading.
16
17
  *
17
18
  * Checks:
19
+ * - All import URIs resolve to existing files
18
20
  * - External imports require manifest + alias
19
21
  * - Local path dependencies stay inside workspace
20
22
  * - Lock file exists for external dependencies
21
23
  */
22
24
  export class ImportValidator {
23
25
  private readonly workspaceManager: WorkspaceManager;
26
+ private readonly importResolver: ImportResolver;
24
27
 
25
28
  constructor(services: DomainLangServices) {
26
29
  this.workspaceManager = services.imports.WorkspaceManager;
30
+ this.importResolver = services.imports.ImportResolver;
27
31
  }
28
32
 
29
33
  /**
@@ -48,6 +52,13 @@ export class ImportValidator {
48
52
  return;
49
53
  }
50
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
+
51
62
  if (!this.isExternalImport(imp.uri)) {
52
63
  return;
53
64
  }
@@ -117,6 +128,63 @@ export class ImportValidator {
117
128
  return true;
118
129
  }
119
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
+
120
188
  /**
121
189
  * Gets the normalized dependency configuration for an alias.
122
190
  */
@@ -296,8 +364,13 @@ export function createImportChecks(services: DomainLangServices): ValidationChec
296
364
  return {
297
365
  // Langium 4.x supports async validators via MaybePromise<void>
298
366
  ImportStatement: async (imp, accept, cancelToken) => {
299
- const document = imp.$document;
300
- 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
+ }
301
374
 
302
375
  await validator.checkImportPath(imp, accept, document, cancelToken);
303
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
  ];