@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.
- package/README.md +1 -1
- package/out/domain-lang-module.js +5 -1
- package/out/domain-lang-module.js.map +1 -1
- package/out/generated/ast.d.ts +24 -0
- package/out/generated/ast.js.map +1 -1
- package/out/generated/grammar.js +22 -32
- package/out/generated/grammar.js.map +1 -1
- package/out/index.d.ts +2 -5
- package/out/index.js +10 -6
- package/out/index.js.map +1 -1
- package/out/lsp/domain-lang-code-actions.js +14 -8
- package/out/lsp/domain-lang-code-actions.js.map +1 -1
- package/out/lsp/domain-lang-completion.d.ts +3 -0
- package/out/lsp/domain-lang-completion.js +41 -13
- package/out/lsp/domain-lang-completion.js.map +1 -1
- package/out/lsp/domain-lang-formatter.js +24 -18
- package/out/lsp/domain-lang-formatter.js.map +1 -1
- package/out/lsp/domain-lang-index-manager.d.ts +170 -0
- package/out/lsp/domain-lang-index-manager.js +389 -0
- package/out/lsp/domain-lang-index-manager.js.map +1 -0
- package/out/lsp/domain-lang-scope-provider.d.ts +67 -0
- package/out/lsp/domain-lang-scope-provider.js +95 -0
- package/out/lsp/domain-lang-scope-provider.js.map +1 -0
- package/out/lsp/domain-lang-scope.js +31 -17
- package/out/lsp/domain-lang-scope.js.map +1 -1
- package/out/lsp/domain-lang-workspace-manager.d.ts +76 -9
- package/out/lsp/domain-lang-workspace-manager.js +176 -54
- package/out/lsp/domain-lang-workspace-manager.js.map +1 -1
- package/out/lsp/hover/domain-lang-hover.d.ts +45 -1
- package/out/lsp/hover/domain-lang-hover.js +308 -232
- package/out/lsp/hover/domain-lang-hover.js.map +1 -1
- package/out/lsp/hover/domain-lang-keywords.d.ts +3 -7
- package/out/lsp/hover/domain-lang-keywords.js +115 -38
- package/out/lsp/hover/domain-lang-keywords.js.map +1 -1
- package/out/lsp/manifest-diagnostics.js +95 -50
- package/out/lsp/manifest-diagnostics.js.map +1 -1
- package/out/main.js +204 -17
- package/out/main.js.map +1 -1
- package/out/services/import-resolver.d.ts +39 -2
- package/out/services/import-resolver.js +77 -12
- package/out/services/import-resolver.js.map +1 -1
- package/out/services/types.d.ts +2 -2
- package/out/services/workspace-manager.d.ts +33 -31
- package/out/services/workspace-manager.js +92 -148
- package/out/services/workspace-manager.js.map +1 -1
- package/out/utils/document-utils.d.ts +41 -0
- package/out/utils/document-utils.js +64 -0
- package/out/utils/document-utils.js.map +1 -0
- package/out/utils/import-utils.d.ts +0 -17
- package/out/utils/import-utils.js +2 -32
- package/out/utils/import-utils.js.map +1 -1
- package/out/utils/manifest-utils.d.ts +56 -0
- package/out/utils/manifest-utils.js +119 -0
- package/out/utils/manifest-utils.js.map +1 -0
- package/out/validation/constants.d.ts +13 -0
- package/out/validation/constants.js +18 -0
- package/out/validation/constants.js.map +1 -1
- package/out/validation/import.d.ts +12 -2
- package/out/validation/import.js +95 -22
- package/out/validation/import.js.map +1 -1
- package/out/validation/maps.js +51 -2
- package/out/validation/maps.js.map +1 -1
- package/package.json +1 -1
- package/src/domain-lang-module.ts +6 -1
- package/src/domain-lang.langium +37 -13
- package/src/generated/ast.ts +24 -0
- package/src/generated/grammar.ts +22 -32
- package/src/index.ts +12 -6
- package/src/lsp/domain-lang-code-actions.ts +13 -8
- package/src/lsp/domain-lang-completion.ts +61 -13
- package/src/lsp/domain-lang-formatter.ts +28 -23
- package/src/lsp/domain-lang-index-manager.ts +447 -0
- package/src/lsp/domain-lang-scope-provider.ts +134 -0
- package/src/lsp/domain-lang-scope.ts +29 -17
- package/src/lsp/domain-lang-workspace-manager.ts +201 -53
- package/src/lsp/hover/domain-lang-hover.ts +332 -226
- package/src/lsp/hover/domain-lang-keywords.ts +129 -43
- package/src/lsp/manifest-diagnostics.ts +100 -59
- package/src/main.ts +258 -16
- package/src/services/import-resolver.ts +91 -12
- package/src/services/types.ts +2 -2
- package/src/services/workspace-manager.ts +101 -175
- package/src/utils/document-utils.ts +80 -0
- package/src/utils/import-utils.ts +2 -40
- package/src/utils/manifest-utils.ts +132 -0
- package/src/validation/constants.ts +24 -0
- package/src/validation/import.ts +107 -24
- package/src/validation/maps.ts +59 -2
- package/out/lsp/hover/ddd-pattern-explanations.d.ts +0 -50
- package/out/lsp/hover/ddd-pattern-explanations.js +0 -196
- package/out/lsp/hover/ddd-pattern-explanations.js.map +0 -1
- package/out/services/dependency-analyzer.d.ts +0 -58
- package/out/services/dependency-analyzer.js +0 -254
- package/out/services/dependency-analyzer.js.map +0 -1
- package/out/services/dependency-resolver.d.ts +0 -146
- package/out/services/dependency-resolver.js +0 -452
- package/out/services/dependency-resolver.js.map +0 -1
- package/out/services/git-url-resolver.browser.d.ts +0 -10
- package/out/services/git-url-resolver.browser.js +0 -19
- package/out/services/git-url-resolver.browser.js.map +0 -1
- package/out/services/git-url-resolver.d.ts +0 -158
- package/out/services/git-url-resolver.js +0 -416
- package/out/services/git-url-resolver.js.map +0 -1
- package/out/services/governance-validator.d.ts +0 -44
- package/out/services/governance-validator.js +0 -153
- package/out/services/governance-validator.js.map +0 -1
- package/out/services/semver.d.ts +0 -98
- package/out/services/semver.js +0 -195
- package/out/services/semver.js.map +0 -1
- package/src/lsp/hover/ddd-pattern-explanations.ts +0 -237
- package/src/services/dependency-analyzer.ts +0 -321
- package/src/services/dependency-resolver.ts +0 -551
- package/src/services/git-url-resolver.browser.ts +0 -26
- package/src/services/git-url-resolver.ts +0 -517
- package/src/services/governance-validator.ts +0 -177
- 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
|
// ========================================================================
|
package/src/validation/import.ts
CHANGED
|
@@ -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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
242
|
-
|
|
314
|
+
try {
|
|
315
|
+
const workspaceRoot = this.workspaceManager.getWorkspaceRoot();
|
|
316
|
+
const cacheDir = this.getCacheDirectory(workspaceRoot, packageKey, lockedDep.commit);
|
|
243
317
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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
|
-
|
|
290
|
-
|
|
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
|
}
|
package/src/validation/maps.ts
CHANGED
|
@@ -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;
|