@domainlang/language 0.8.0 → 0.9.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/out/sdk/index.d.ts +29 -10
- package/out/sdk/index.js +29 -10
- package/out/sdk/index.js.map +1 -1
- package/out/sdk/loader-node.d.ts +2 -0
- package/out/sdk/loader-node.js +2 -0
- package/out/sdk/loader-node.js.map +1 -1
- package/out/sdk/validator.d.ts +134 -0
- package/out/sdk/validator.js +249 -0
- package/out/sdk/validator.js.map +1 -0
- package/package.json +4 -5
- package/src/sdk/index.ts +31 -10
- package/src/sdk/loader-node.ts +4 -0
- package/src/sdk/validator.ts +358 -0
package/out/sdk/index.d.ts
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* - Fluent query chains with lazy iteration
|
|
17
17
|
* - O(1) indexed lookups by FQN/name
|
|
18
18
|
* - Resolution rules (which block wins for 0..1 properties)
|
|
19
|
+
* - File validation (Node.js only, via `validateFile()`)
|
|
19
20
|
*
|
|
20
21
|
* **Entry points for different deployment targets:**
|
|
21
22
|
*
|
|
@@ -23,27 +24,24 @@
|
|
|
23
24
|
* |--------|-------------|--------------|-------|
|
|
24
25
|
* | VS Code Extension | `fromDocument()` | ✅ | Zero-copy LSP integration |
|
|
25
26
|
* | Web Editor | `fromDocument()`, `loadModelFromText()` | ✅ | Browser-compatible |
|
|
26
|
-
* | CLI (Node.js) | `loadModel()
|
|
27
|
+
* | CLI (Node.js) | `loadModel()`, `validateFile()` | ❌ | File system access |
|
|
27
28
|
* | Hosted LSP | `fromDocument()`, `fromServices()` | ✅ | Server-side only |
|
|
28
29
|
* | Testing | `loadModelFromText()` | ✅ | In-memory parsing |
|
|
29
30
|
*
|
|
30
31
|
* ## Browser vs Node.js
|
|
31
32
|
*
|
|
32
|
-
*
|
|
33
|
-
* - `
|
|
34
|
-
* - `
|
|
33
|
+
* Most of this module is **browser-safe**, but Node.js-specific functions are exported as well:
|
|
34
|
+
* - `loadModel()` - requires Node.js file system (uses NodeFileSystem)
|
|
35
|
+
* - `validateFile()` - requires Node.js file system (uses NodeFileSystem)
|
|
35
36
|
*
|
|
36
|
-
*
|
|
37
|
-
* ```typescript
|
|
38
|
-
* import { loadModel } from 'domain-lang-language/sdk/loader-node';
|
|
39
|
-
* ```
|
|
37
|
+
* These will fail at runtime in browser environments.
|
|
40
38
|
*
|
|
41
39
|
* @packageDocumentation
|
|
42
40
|
*
|
|
43
41
|
* @example
|
|
44
42
|
* ```typescript
|
|
45
|
-
* // Node.js CLI: Load from file
|
|
46
|
-
* import { loadModel } from '
|
|
43
|
+
* // Node.js CLI: Load from file
|
|
44
|
+
* import { loadModel } from '@domainlang/language/sdk';
|
|
47
45
|
*
|
|
48
46
|
* const { query } = await loadModel('./domains.dlang', {
|
|
49
47
|
* workspaceDir: process.cwd()
|
|
@@ -60,6 +58,24 @@
|
|
|
60
58
|
*
|
|
61
59
|
* @example
|
|
62
60
|
* ```typescript
|
|
61
|
+
* // Node.js CLI: Validate a model (requires sdk/loader-node)
|
|
62
|
+
* import { validateFile } from '@domainlang/language/sdk';
|
|
63
|
+
*
|
|
64
|
+
* const result = await validateFile('./domains.dlang');
|
|
65
|
+
*
|
|
66
|
+
* if (!result.valid) {
|
|
67
|
+
* for (const error of result.errors) {
|
|
68
|
+
* console.error(`${error.file}:${error.line}: ${error.message}`);
|
|
69
|
+
* }
|
|
70
|
+
* process.exit(1);
|
|
71
|
+
* }
|
|
72
|
+
*
|
|
73
|
+
* console.log(`✓ Validated ${result.fileCount} files`);
|
|
74
|
+
* console.log(` ${result.domainCount} domains, ${result.bcCount} bounded contexts`);
|
|
75
|
+
* ```
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```typescript
|
|
63
79
|
* // Browser/Testing: Load from text (browser-safe)
|
|
64
80
|
* import { loadModelFromText } from '@domainlang/language/sdk';
|
|
65
81
|
*
|
|
@@ -93,3 +109,6 @@ export { fromModel, fromDocument, fromServices, augmentModel } from './query.js'
|
|
|
93
109
|
export { Pattern, PatternFullName, PatternAliases, matchesPattern, isUpstreamPattern, isDownstreamPattern, isMutualPattern, UpstreamPatterns, DownstreamPatterns, MutualPatterns, } from './patterns.js';
|
|
94
110
|
export type { IntegrationPattern } from './patterns.js';
|
|
95
111
|
export type { Query, QueryBuilder, QueryContext, LoadOptions, BcQueryBuilder, RelationshipView, } from './types.js';
|
|
112
|
+
export { loadModel } from './loader-node.js';
|
|
113
|
+
export { validateFile, validateWorkspace } from './validator.js';
|
|
114
|
+
export type { ValidationResult, ValidationDiagnostic, ValidationOptions, WorkspaceValidationResult } from './validator.js';
|
package/out/sdk/index.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* - Fluent query chains with lazy iteration
|
|
17
17
|
* - O(1) indexed lookups by FQN/name
|
|
18
18
|
* - Resolution rules (which block wins for 0..1 properties)
|
|
19
|
+
* - File validation (Node.js only, via `validateFile()`)
|
|
19
20
|
*
|
|
20
21
|
* **Entry points for different deployment targets:**
|
|
21
22
|
*
|
|
@@ -23,27 +24,24 @@
|
|
|
23
24
|
* |--------|-------------|--------------|-------|
|
|
24
25
|
* | VS Code Extension | `fromDocument()` | ✅ | Zero-copy LSP integration |
|
|
25
26
|
* | Web Editor | `fromDocument()`, `loadModelFromText()` | ✅ | Browser-compatible |
|
|
26
|
-
* | CLI (Node.js) | `loadModel()
|
|
27
|
+
* | CLI (Node.js) | `loadModel()`, `validateFile()` | ❌ | File system access |
|
|
27
28
|
* | Hosted LSP | `fromDocument()`, `fromServices()` | ✅ | Server-side only |
|
|
28
29
|
* | Testing | `loadModelFromText()` | ✅ | In-memory parsing |
|
|
29
30
|
*
|
|
30
31
|
* ## Browser vs Node.js
|
|
31
32
|
*
|
|
32
|
-
*
|
|
33
|
-
* - `
|
|
34
|
-
* - `
|
|
33
|
+
* Most of this module is **browser-safe**, but Node.js-specific functions are exported as well:
|
|
34
|
+
* - `loadModel()` - requires Node.js file system (uses NodeFileSystem)
|
|
35
|
+
* - `validateFile()` - requires Node.js file system (uses NodeFileSystem)
|
|
35
36
|
*
|
|
36
|
-
*
|
|
37
|
-
* ```typescript
|
|
38
|
-
* import { loadModel } from 'domain-lang-language/sdk/loader-node';
|
|
39
|
-
* ```
|
|
37
|
+
* These will fail at runtime in browser environments.
|
|
40
38
|
*
|
|
41
39
|
* @packageDocumentation
|
|
42
40
|
*
|
|
43
41
|
* @example
|
|
44
42
|
* ```typescript
|
|
45
|
-
* // Node.js CLI: Load from file
|
|
46
|
-
* import { loadModel } from '
|
|
43
|
+
* // Node.js CLI: Load from file
|
|
44
|
+
* import { loadModel } from '@domainlang/language/sdk';
|
|
47
45
|
*
|
|
48
46
|
* const { query } = await loadModel('./domains.dlang', {
|
|
49
47
|
* workspaceDir: process.cwd()
|
|
@@ -60,6 +58,24 @@
|
|
|
60
58
|
*
|
|
61
59
|
* @example
|
|
62
60
|
* ```typescript
|
|
61
|
+
* // Node.js CLI: Validate a model (requires sdk/loader-node)
|
|
62
|
+
* import { validateFile } from '@domainlang/language/sdk';
|
|
63
|
+
*
|
|
64
|
+
* const result = await validateFile('./domains.dlang');
|
|
65
|
+
*
|
|
66
|
+
* if (!result.valid) {
|
|
67
|
+
* for (const error of result.errors) {
|
|
68
|
+
* console.error(`${error.file}:${error.line}: ${error.message}`);
|
|
69
|
+
* }
|
|
70
|
+
* process.exit(1);
|
|
71
|
+
* }
|
|
72
|
+
*
|
|
73
|
+
* console.log(`✓ Validated ${result.fileCount} files`);
|
|
74
|
+
* console.log(` ${result.domainCount} domains, ${result.bcCount} bounded contexts`);
|
|
75
|
+
* ```
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```typescript
|
|
63
79
|
* // Browser/Testing: Load from text (browser-safe)
|
|
64
80
|
* import { loadModelFromText } from '@domainlang/language/sdk';
|
|
65
81
|
*
|
|
@@ -94,4 +110,7 @@ export { fromModel, fromDocument, fromServices, augmentModel } from './query.js'
|
|
|
94
110
|
// For CLI/Node.js usage: import { loadModel } from '@domainlang/language/sdk/loader-node';
|
|
95
111
|
// Integration patterns for type-safe pattern matching (no magic strings)
|
|
96
112
|
export { Pattern, PatternFullName, PatternAliases, matchesPattern, isUpstreamPattern, isDownstreamPattern, isMutualPattern, UpstreamPatterns, DownstreamPatterns, MutualPatterns, } from './patterns.js';
|
|
113
|
+
// Node.js-specific exports (will fail in browser environments)
|
|
114
|
+
export { loadModel } from './loader-node.js';
|
|
115
|
+
export { validateFile, validateWorkspace } from './validator.js';
|
|
97
116
|
//# sourceMappingURL=index.js.map
|
package/out/sdk/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sdk/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sdk/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwGG;AAEH,4BAA4B;AAC5B,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEnE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEjF,0EAA0E;AAC1E,2FAA2F;AAE3F,yEAAyE;AACzE,OAAO,EACH,OAAO,EACP,eAAe,EACf,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,EACf,gBAAgB,EAChB,kBAAkB,EAClB,cAAc,GACjB,MAAM,eAAe,CAAC;AAkBvB,+DAA+D;AAC/D,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC"}
|
package/out/sdk/loader-node.d.ts
CHANGED
|
@@ -49,3 +49,5 @@ import type { LoadOptions, QueryContext } from './types.js';
|
|
|
49
49
|
* ```
|
|
50
50
|
*/
|
|
51
51
|
export declare function loadModel(entryFile: string, options?: LoadOptions): Promise<QueryContext>;
|
|
52
|
+
export { validateFile } from './validator.js';
|
|
53
|
+
export type { ValidationResult, ValidationDiagnostic, ValidationOptions } from './validator.js';
|
package/out/sdk/loader-node.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader-node.js","sourceRoot":"","sources":["../../src/sdk/loader-node.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"loader-node.js","sourceRoot":"","sources":["../../src/sdk/loader-node.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model validation utilities for Node.js environments.
|
|
3
|
+
*
|
|
4
|
+
* **WARNING: This module is NOT browser-compatible.**
|
|
5
|
+
*
|
|
6
|
+
* Provides validation capabilities that leverage the LSP infrastructure
|
|
7
|
+
* for workspace initialization, import resolution, and document building.
|
|
8
|
+
*
|
|
9
|
+
* @module sdk/validator
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Validation diagnostic with file context.
|
|
13
|
+
*/
|
|
14
|
+
export interface ValidationDiagnostic {
|
|
15
|
+
/** Diagnostic severity (1=error, 2=warning, 3=info, 4=hint) */
|
|
16
|
+
severity: number;
|
|
17
|
+
/** Diagnostic message */
|
|
18
|
+
message: string;
|
|
19
|
+
/** File path where diagnostic occurred */
|
|
20
|
+
file: string;
|
|
21
|
+
/** Line number (1-based) */
|
|
22
|
+
line: number;
|
|
23
|
+
/** Column number (1-based) */
|
|
24
|
+
column: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Result of model validation.
|
|
28
|
+
*/
|
|
29
|
+
export interface ValidationResult {
|
|
30
|
+
/** Whether the model is valid (no errors) */
|
|
31
|
+
valid: boolean;
|
|
32
|
+
/** Number of files validated */
|
|
33
|
+
fileCount: number;
|
|
34
|
+
/** Number of domains in the model */
|
|
35
|
+
domainCount: number;
|
|
36
|
+
/** Number of bounded contexts in the model */
|
|
37
|
+
bcCount: number;
|
|
38
|
+
/** Validation errors */
|
|
39
|
+
errors: ValidationDiagnostic[];
|
|
40
|
+
/** Validation warnings */
|
|
41
|
+
warnings: ValidationDiagnostic[];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Options for validation.
|
|
45
|
+
*/
|
|
46
|
+
export interface ValidationOptions {
|
|
47
|
+
/** Workspace directory (defaults to file's directory) */
|
|
48
|
+
workspaceDir?: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Validates a DomainLang model file and all its imports.
|
|
52
|
+
*
|
|
53
|
+
* Uses the LSP infrastructure to:
|
|
54
|
+
* - Initialize the workspace
|
|
55
|
+
* - Resolve and load imports
|
|
56
|
+
* - Build and validate all documents
|
|
57
|
+
*
|
|
58
|
+
* @param filePath - Path to the entry .dlang file
|
|
59
|
+
* @param options - Validation options
|
|
60
|
+
* @returns Validation result with errors, warnings, and model statistics
|
|
61
|
+
* @throws Error if file doesn't exist or has invalid extension
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```typescript
|
|
65
|
+
* import { validateFile } from '@domainlang/language/sdk';
|
|
66
|
+
*
|
|
67
|
+
* const result = await validateFile('./index.dlang');
|
|
68
|
+
*
|
|
69
|
+
* if (!result.valid) {
|
|
70
|
+
* for (const err of result.errors) {
|
|
71
|
+
* console.error(`${err.file}:${err.line}:${err.column}: ${err.message}`);
|
|
72
|
+
* }
|
|
73
|
+
* process.exit(1);
|
|
74
|
+
* }
|
|
75
|
+
*
|
|
76
|
+
* console.log(`✓ Validated ${result.fileCount} files`);
|
|
77
|
+
* console.log(` ${result.domainCount} domains, ${result.bcCount} bounded contexts`);
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
export declare function validateFile(filePath: string, options?: ValidationOptions): Promise<ValidationResult>;
|
|
81
|
+
/**
|
|
82
|
+
* Workspace validation result with diagnostics grouped by file.
|
|
83
|
+
*/
|
|
84
|
+
export interface WorkspaceValidationResult {
|
|
85
|
+
/** Whether the workspace is valid (no errors in any file) */
|
|
86
|
+
valid: boolean;
|
|
87
|
+
/** Number of files validated */
|
|
88
|
+
fileCount: number;
|
|
89
|
+
/** Number of domains across all files */
|
|
90
|
+
domainCount: number;
|
|
91
|
+
/** Number of bounded contexts across all files */
|
|
92
|
+
bcCount: number;
|
|
93
|
+
/** Validation errors grouped by file path */
|
|
94
|
+
errors: ValidationDiagnostic[];
|
|
95
|
+
/** Validation warnings grouped by file path */
|
|
96
|
+
warnings: ValidationDiagnostic[];
|
|
97
|
+
/** Total number of diagnostics across all files */
|
|
98
|
+
totalDiagnostics: number;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Validates an entire DomainLang workspace.
|
|
102
|
+
*
|
|
103
|
+
* Uses the LSP infrastructure to:
|
|
104
|
+
* - Initialize the workspace from a directory containing model.yaml
|
|
105
|
+
* - Load the entry file (from manifest or default index.dlang)
|
|
106
|
+
* - Resolve and load all imports
|
|
107
|
+
* - Build and validate all documents in the workspace
|
|
108
|
+
* - Collect diagnostics from ALL documents (like VS Code Problems pane)
|
|
109
|
+
*
|
|
110
|
+
* @param workspaceDir - Path to the workspace directory (containing model.yaml)
|
|
111
|
+
* @returns Validation result with diagnostics from all files
|
|
112
|
+
* @throws Error if workspace directory doesn't exist or cannot be loaded
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```typescript
|
|
116
|
+
* import { validateWorkspace } from '@domainlang/language/sdk';
|
|
117
|
+
*
|
|
118
|
+
* const result = await validateWorkspace('./my-workspace');
|
|
119
|
+
*
|
|
120
|
+
* if (!result.valid) {
|
|
121
|
+
* console.error(`Found ${result.errors.length} errors in ${result.fileCount} files`);
|
|
122
|
+
*
|
|
123
|
+
* for (const err of result.errors) {
|
|
124
|
+
* console.error(`${err.file}:${err.line}:${err.column}: ${err.message}`);
|
|
125
|
+
* }
|
|
126
|
+
* process.exit(1);
|
|
127
|
+
* }
|
|
128
|
+
*
|
|
129
|
+
* console.log(`✓ Validated ${result.fileCount} files`);
|
|
130
|
+
* console.log(` ${result.domainCount} domains, ${result.bcCount} bounded contexts`);
|
|
131
|
+
* console.log(` 0 errors, ${result.warnings.length} warnings`);
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
export declare function validateWorkspace(workspaceDir: string): Promise<WorkspaceValidationResult>;
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model validation utilities for Node.js environments.
|
|
3
|
+
*
|
|
4
|
+
* **WARNING: This module is NOT browser-compatible.**
|
|
5
|
+
*
|
|
6
|
+
* Provides validation capabilities that leverage the LSP infrastructure
|
|
7
|
+
* for workspace initialization, import resolution, and document building.
|
|
8
|
+
*
|
|
9
|
+
* @module sdk/validator
|
|
10
|
+
*/
|
|
11
|
+
import { NodeFileSystem } from 'langium/node';
|
|
12
|
+
import { URI } from 'langium';
|
|
13
|
+
import { createDomainLangServices } from '../domain-lang-module.js';
|
|
14
|
+
import { ensureImportGraphFromDocument } from '../utils/import-utils.js';
|
|
15
|
+
import { isModel } from '../generated/ast.js';
|
|
16
|
+
import { dirname, resolve, join } from 'node:path';
|
|
17
|
+
import { existsSync } from 'node:fs';
|
|
18
|
+
/**
|
|
19
|
+
* Convert Langium diagnostic to ValidationDiagnostic.
|
|
20
|
+
*/
|
|
21
|
+
function toValidationDiagnostic(diagnostic, file) {
|
|
22
|
+
return {
|
|
23
|
+
severity: diagnostic.severity ?? 1,
|
|
24
|
+
message: diagnostic.message,
|
|
25
|
+
file,
|
|
26
|
+
line: diagnostic.range.start.line + 1,
|
|
27
|
+
column: diagnostic.range.start.character + 1,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Validates a DomainLang model file and all its imports.
|
|
32
|
+
*
|
|
33
|
+
* Uses the LSP infrastructure to:
|
|
34
|
+
* - Initialize the workspace
|
|
35
|
+
* - Resolve and load imports
|
|
36
|
+
* - Build and validate all documents
|
|
37
|
+
*
|
|
38
|
+
* @param filePath - Path to the entry .dlang file
|
|
39
|
+
* @param options - Validation options
|
|
40
|
+
* @returns Validation result with errors, warnings, and model statistics
|
|
41
|
+
* @throws Error if file doesn't exist or has invalid extension
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```typescript
|
|
45
|
+
* import { validateFile } from '@domainlang/language/sdk';
|
|
46
|
+
*
|
|
47
|
+
* const result = await validateFile('./index.dlang');
|
|
48
|
+
*
|
|
49
|
+
* if (!result.valid) {
|
|
50
|
+
* for (const err of result.errors) {
|
|
51
|
+
* console.error(`${err.file}:${err.line}:${err.column}: ${err.message}`);
|
|
52
|
+
* }
|
|
53
|
+
* process.exit(1);
|
|
54
|
+
* }
|
|
55
|
+
*
|
|
56
|
+
* console.log(`✓ Validated ${result.fileCount} files`);
|
|
57
|
+
* console.log(` ${result.domainCount} domains, ${result.bcCount} bounded contexts`);
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export async function validateFile(filePath, options = {}) {
|
|
61
|
+
// Resolve absolute path
|
|
62
|
+
const absolutePath = resolve(filePath);
|
|
63
|
+
// Check file exists
|
|
64
|
+
if (!existsSync(absolutePath)) {
|
|
65
|
+
throw new Error(`File not found: ${filePath}`);
|
|
66
|
+
}
|
|
67
|
+
// Create services with workspace support
|
|
68
|
+
const servicesObj = createDomainLangServices(NodeFileSystem);
|
|
69
|
+
const shared = servicesObj.shared;
|
|
70
|
+
const services = servicesObj.DomainLang;
|
|
71
|
+
// Check file extension
|
|
72
|
+
const extensions = services.LanguageMetaData.fileExtensions;
|
|
73
|
+
if (!extensions.some(ext => absolutePath.endsWith(ext))) {
|
|
74
|
+
throw new Error(`Invalid file extension. Expected: ${extensions.join(', ')}`);
|
|
75
|
+
}
|
|
76
|
+
// Initialize workspace with the specified directory or file's directory
|
|
77
|
+
const workspaceDir = options.workspaceDir ?? dirname(absolutePath);
|
|
78
|
+
const workspaceManager = services.imports.WorkspaceManager;
|
|
79
|
+
await workspaceManager.initialize(workspaceDir);
|
|
80
|
+
// Load and parse the document
|
|
81
|
+
const uri = URI.file(absolutePath);
|
|
82
|
+
const document = await shared.workspace.LangiumDocuments.getOrCreateDocument(uri);
|
|
83
|
+
// Build document initially without validation to load imports
|
|
84
|
+
await shared.workspace.DocumentBuilder.build([document], { validation: false });
|
|
85
|
+
// Load all imported documents via the import graph
|
|
86
|
+
const importResolver = services.imports.ImportResolver;
|
|
87
|
+
await ensureImportGraphFromDocument(document, shared.workspace.LangiumDocuments, importResolver);
|
|
88
|
+
// Build all documents with validation enabled
|
|
89
|
+
const allDocuments = Array.from(shared.workspace.LangiumDocuments.all);
|
|
90
|
+
await shared.workspace.DocumentBuilder.build(allDocuments, { validation: true });
|
|
91
|
+
// Collect diagnostics from the entry document
|
|
92
|
+
const diagnostics = document.diagnostics ?? [];
|
|
93
|
+
const errors = [];
|
|
94
|
+
const warnings = [];
|
|
95
|
+
for (const diagnostic of diagnostics) {
|
|
96
|
+
const validationDiag = toValidationDiagnostic(diagnostic, absolutePath);
|
|
97
|
+
if (diagnostic.severity === 1) {
|
|
98
|
+
errors.push(validationDiag);
|
|
99
|
+
}
|
|
100
|
+
else if (diagnostic.severity === 2) {
|
|
101
|
+
warnings.push(validationDiag);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Count model elements across all documents
|
|
105
|
+
let domainCount = 0;
|
|
106
|
+
let bcCount = 0;
|
|
107
|
+
for (const doc of allDocuments) {
|
|
108
|
+
const model = doc.parseResult?.value;
|
|
109
|
+
if (isModel(model)) {
|
|
110
|
+
for (const element of model.children ?? []) {
|
|
111
|
+
if (element.$type === 'Domain') {
|
|
112
|
+
domainCount++;
|
|
113
|
+
}
|
|
114
|
+
else if (element.$type === 'BoundedContext') {
|
|
115
|
+
bcCount++;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
valid: errors.length === 0,
|
|
122
|
+
fileCount: allDocuments.length,
|
|
123
|
+
domainCount,
|
|
124
|
+
bcCount,
|
|
125
|
+
errors,
|
|
126
|
+
warnings,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Validates an entire DomainLang workspace.
|
|
131
|
+
*
|
|
132
|
+
* Uses the LSP infrastructure to:
|
|
133
|
+
* - Initialize the workspace from a directory containing model.yaml
|
|
134
|
+
* - Load the entry file (from manifest or default index.dlang)
|
|
135
|
+
* - Resolve and load all imports
|
|
136
|
+
* - Build and validate all documents in the workspace
|
|
137
|
+
* - Collect diagnostics from ALL documents (like VS Code Problems pane)
|
|
138
|
+
*
|
|
139
|
+
* @param workspaceDir - Path to the workspace directory (containing model.yaml)
|
|
140
|
+
* @returns Validation result with diagnostics from all files
|
|
141
|
+
* @throws Error if workspace directory doesn't exist or cannot be loaded
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```typescript
|
|
145
|
+
* import { validateWorkspace } from '@domainlang/language/sdk';
|
|
146
|
+
*
|
|
147
|
+
* const result = await validateWorkspace('./my-workspace');
|
|
148
|
+
*
|
|
149
|
+
* if (!result.valid) {
|
|
150
|
+
* console.error(`Found ${result.errors.length} errors in ${result.fileCount} files`);
|
|
151
|
+
*
|
|
152
|
+
* for (const err of result.errors) {
|
|
153
|
+
* console.error(`${err.file}:${err.line}:${err.column}: ${err.message}`);
|
|
154
|
+
* }
|
|
155
|
+
* process.exit(1);
|
|
156
|
+
* }
|
|
157
|
+
*
|
|
158
|
+
* console.log(`✓ Validated ${result.fileCount} files`);
|
|
159
|
+
* console.log(` ${result.domainCount} domains, ${result.bcCount} bounded contexts`);
|
|
160
|
+
* console.log(` 0 errors, ${result.warnings.length} warnings`);
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
export async function validateWorkspace(workspaceDir) {
|
|
164
|
+
// Resolve absolute path
|
|
165
|
+
const absolutePath = resolve(workspaceDir);
|
|
166
|
+
// Check directory exists
|
|
167
|
+
if (!existsSync(absolutePath)) {
|
|
168
|
+
throw new Error(`Workspace directory not found: ${workspaceDir}`);
|
|
169
|
+
}
|
|
170
|
+
// Create services with workspace support
|
|
171
|
+
const servicesObj = createDomainLangServices(NodeFileSystem);
|
|
172
|
+
const shared = servicesObj.shared;
|
|
173
|
+
const services = servicesObj.DomainLang;
|
|
174
|
+
const workspaceManager = services.imports.WorkspaceManager;
|
|
175
|
+
try {
|
|
176
|
+
// Initialize workspace - this will find and load model.yaml
|
|
177
|
+
await workspaceManager.initialize(absolutePath);
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
181
|
+
throw new Error(`Failed to initialize workspace at ${workspaceDir}: ${message}`);
|
|
182
|
+
}
|
|
183
|
+
// Get the manifest to find the entry file
|
|
184
|
+
const manifest = await workspaceManager.getManifest();
|
|
185
|
+
let entryFile = 'index.dlang';
|
|
186
|
+
if (manifest?.model?.entry) {
|
|
187
|
+
entryFile = manifest.model.entry;
|
|
188
|
+
}
|
|
189
|
+
const entryPath = join(absolutePath, entryFile);
|
|
190
|
+
// Check if entry file exists
|
|
191
|
+
if (!existsSync(entryPath)) {
|
|
192
|
+
throw new Error(`Entry file not found: ${entryFile}\n` +
|
|
193
|
+
`Expected at: ${entryPath}\n` +
|
|
194
|
+
(manifest ? `Specified in manifest` : `Using default entry file`));
|
|
195
|
+
}
|
|
196
|
+
// Load and parse the entry document
|
|
197
|
+
const uri = URI.file(entryPath);
|
|
198
|
+
const document = await shared.workspace.LangiumDocuments.getOrCreateDocument(uri);
|
|
199
|
+
// Build document initially without validation to load imports
|
|
200
|
+
await shared.workspace.DocumentBuilder.build([document], { validation: false });
|
|
201
|
+
// Load all imported documents via the import graph
|
|
202
|
+
const importResolver = services.imports.ImportResolver;
|
|
203
|
+
await ensureImportGraphFromDocument(document, shared.workspace.LangiumDocuments, importResolver);
|
|
204
|
+
// Build all documents with validation enabled
|
|
205
|
+
const allDocuments = Array.from(shared.workspace.LangiumDocuments.all);
|
|
206
|
+
await shared.workspace.DocumentBuilder.build(allDocuments, { validation: true });
|
|
207
|
+
// Collect diagnostics from ALL documents (not just entry)
|
|
208
|
+
const errors = [];
|
|
209
|
+
const warnings = [];
|
|
210
|
+
for (const doc of allDocuments) {
|
|
211
|
+
const diagnostics = doc.diagnostics ?? [];
|
|
212
|
+
const docPath = doc.uri.fsPath;
|
|
213
|
+
for (const diagnostic of diagnostics) {
|
|
214
|
+
const validationDiag = toValidationDiagnostic(diagnostic, docPath);
|
|
215
|
+
if (diagnostic.severity === 1) {
|
|
216
|
+
errors.push(validationDiag);
|
|
217
|
+
}
|
|
218
|
+
else if (diagnostic.severity === 2) {
|
|
219
|
+
warnings.push(validationDiag);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// Count model elements across all documents
|
|
224
|
+
let domainCount = 0;
|
|
225
|
+
let bcCount = 0;
|
|
226
|
+
for (const doc of allDocuments) {
|
|
227
|
+
const model = doc.parseResult?.value;
|
|
228
|
+
if (isModel(model)) {
|
|
229
|
+
for (const element of model.children ?? []) {
|
|
230
|
+
if (element.$type === 'Domain') {
|
|
231
|
+
domainCount++;
|
|
232
|
+
}
|
|
233
|
+
else if (element.$type === 'BoundedContext') {
|
|
234
|
+
bcCount++;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
valid: errors.length === 0,
|
|
241
|
+
fileCount: allDocuments.length,
|
|
242
|
+
domainCount,
|
|
243
|
+
bcCount,
|
|
244
|
+
errors,
|
|
245
|
+
warnings,
|
|
246
|
+
totalDiagnostics: errors.length + warnings.length,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
//# sourceMappingURL=validator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validator.js","sourceRoot":"","sources":["../../src/sdk/validator.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@domainlang/language",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"displayName": "DomainLang Language",
|
|
5
5
|
"description": "Core language library for DomainLang - parse, validate, and query Domain-Driven Design models programmatically",
|
|
6
6
|
"author": "larsbaunwall",
|
|
@@ -63,16 +63,15 @@
|
|
|
63
63
|
"./syntaxes/domain-lang.monarch": {
|
|
64
64
|
"types": "./out/syntaxes/domain-lang.monarch.d.ts",
|
|
65
65
|
"default": "./out/syntaxes/domain-lang.monarch.js"
|
|
66
|
-
},
|
|
67
|
-
"./src/services/git-url-resolver.js": {
|
|
68
|
-
"browser": "./src/services/git-url-resolver.browser.js",
|
|
69
|
-
"default": "./src/services/git-url-resolver.js"
|
|
70
66
|
}
|
|
71
67
|
},
|
|
72
68
|
"typesVersions": {
|
|
73
69
|
"*": {
|
|
74
70
|
".": [
|
|
75
71
|
"out/index"
|
|
72
|
+
],
|
|
73
|
+
"sdk": [
|
|
74
|
+
"out/sdk/index"
|
|
76
75
|
]
|
|
77
76
|
}
|
|
78
77
|
},
|
package/src/sdk/index.ts
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* - Fluent query chains with lazy iteration
|
|
17
17
|
* - O(1) indexed lookups by FQN/name
|
|
18
18
|
* - Resolution rules (which block wins for 0..1 properties)
|
|
19
|
+
* - File validation (Node.js only, via `validateFile()`)
|
|
19
20
|
*
|
|
20
21
|
* **Entry points for different deployment targets:**
|
|
21
22
|
*
|
|
@@ -23,27 +24,24 @@
|
|
|
23
24
|
* |--------|-------------|--------------|-------|
|
|
24
25
|
* | VS Code Extension | `fromDocument()` | ✅ | Zero-copy LSP integration |
|
|
25
26
|
* | Web Editor | `fromDocument()`, `loadModelFromText()` | ✅ | Browser-compatible |
|
|
26
|
-
* | CLI (Node.js) | `loadModel()
|
|
27
|
+
* | CLI (Node.js) | `loadModel()`, `validateFile()` | ❌ | File system access |
|
|
27
28
|
* | Hosted LSP | `fromDocument()`, `fromServices()` | ✅ | Server-side only |
|
|
28
29
|
* | Testing | `loadModelFromText()` | ✅ | In-memory parsing |
|
|
29
30
|
*
|
|
30
31
|
* ## Browser vs Node.js
|
|
31
32
|
*
|
|
32
|
-
*
|
|
33
|
-
* - `
|
|
34
|
-
* - `
|
|
33
|
+
* Most of this module is **browser-safe**, but Node.js-specific functions are exported as well:
|
|
34
|
+
* - `loadModel()` - requires Node.js file system (uses NodeFileSystem)
|
|
35
|
+
* - `validateFile()` - requires Node.js file system (uses NodeFileSystem)
|
|
35
36
|
*
|
|
36
|
-
*
|
|
37
|
-
* ```typescript
|
|
38
|
-
* import { loadModel } from 'domain-lang-language/sdk/loader-node';
|
|
39
|
-
* ```
|
|
37
|
+
* These will fail at runtime in browser environments.
|
|
40
38
|
*
|
|
41
39
|
* @packageDocumentation
|
|
42
40
|
*
|
|
43
41
|
* @example
|
|
44
42
|
* ```typescript
|
|
45
|
-
* // Node.js CLI: Load from file
|
|
46
|
-
* import { loadModel } from '
|
|
43
|
+
* // Node.js CLI: Load from file
|
|
44
|
+
* import { loadModel } from '@domainlang/language/sdk';
|
|
47
45
|
*
|
|
48
46
|
* const { query } = await loadModel('./domains.dlang', {
|
|
49
47
|
* workspaceDir: process.cwd()
|
|
@@ -60,6 +58,24 @@
|
|
|
60
58
|
*
|
|
61
59
|
* @example
|
|
62
60
|
* ```typescript
|
|
61
|
+
* // Node.js CLI: Validate a model (requires sdk/loader-node)
|
|
62
|
+
* import { validateFile } from '@domainlang/language/sdk';
|
|
63
|
+
*
|
|
64
|
+
* const result = await validateFile('./domains.dlang');
|
|
65
|
+
*
|
|
66
|
+
* if (!result.valid) {
|
|
67
|
+
* for (const error of result.errors) {
|
|
68
|
+
* console.error(`${error.file}:${error.line}: ${error.message}`);
|
|
69
|
+
* }
|
|
70
|
+
* process.exit(1);
|
|
71
|
+
* }
|
|
72
|
+
*
|
|
73
|
+
* console.log(`✓ Validated ${result.fileCount} files`);
|
|
74
|
+
* console.log(` ${result.domainCount} domains, ${result.bcCount} bounded contexts`);
|
|
75
|
+
* ```
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```typescript
|
|
63
79
|
* // Browser/Testing: Load from text (browser-safe)
|
|
64
80
|
* import { loadModelFromText } from '@domainlang/language/sdk';
|
|
65
81
|
*
|
|
@@ -125,3 +141,8 @@ export type {
|
|
|
125
141
|
BcQueryBuilder,
|
|
126
142
|
RelationshipView,
|
|
127
143
|
} from './types.js';
|
|
144
|
+
|
|
145
|
+
// Node.js-specific exports (will fail in browser environments)
|
|
146
|
+
export { loadModel } from './loader-node.js';
|
|
147
|
+
export { validateFile, validateWorkspace } from './validator.js';
|
|
148
|
+
export type { ValidationResult, ValidationDiagnostic, ValidationOptions, WorkspaceValidationResult } from './validator.js';
|
package/src/sdk/loader-node.ts
CHANGED
|
@@ -145,3 +145,7 @@ export async function loadModel(
|
|
|
145
145
|
query: fromModel(model),
|
|
146
146
|
};
|
|
147
147
|
}
|
|
148
|
+
|
|
149
|
+
// Re-export validation utilities
|
|
150
|
+
export { validateFile } from './validator.js';
|
|
151
|
+
export type { ValidationResult, ValidationDiagnostic, ValidationOptions } from './validator.js';
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model validation utilities for Node.js environments.
|
|
3
|
+
*
|
|
4
|
+
* **WARNING: This module is NOT browser-compatible.**
|
|
5
|
+
*
|
|
6
|
+
* Provides validation capabilities that leverage the LSP infrastructure
|
|
7
|
+
* for workspace initialization, import resolution, and document building.
|
|
8
|
+
*
|
|
9
|
+
* @module sdk/validator
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { NodeFileSystem } from 'langium/node';
|
|
13
|
+
import { URI } from 'langium';
|
|
14
|
+
import { createDomainLangServices } from '../domain-lang-module.js';
|
|
15
|
+
import { ensureImportGraphFromDocument } from '../utils/import-utils.js';
|
|
16
|
+
import { isModel } from '../generated/ast.js';
|
|
17
|
+
import { dirname, resolve, join } from 'node:path';
|
|
18
|
+
import { existsSync } from 'node:fs';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Validation diagnostic with file context.
|
|
22
|
+
*/
|
|
23
|
+
export interface ValidationDiagnostic {
|
|
24
|
+
/** Diagnostic severity (1=error, 2=warning, 3=info, 4=hint) */
|
|
25
|
+
severity: number;
|
|
26
|
+
/** Diagnostic message */
|
|
27
|
+
message: string;
|
|
28
|
+
/** File path where diagnostic occurred */
|
|
29
|
+
file: string;
|
|
30
|
+
/** Line number (1-based) */
|
|
31
|
+
line: number;
|
|
32
|
+
/** Column number (1-based) */
|
|
33
|
+
column: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Result of model validation.
|
|
38
|
+
*/
|
|
39
|
+
export interface ValidationResult {
|
|
40
|
+
/** Whether the model is valid (no errors) */
|
|
41
|
+
valid: boolean;
|
|
42
|
+
/** Number of files validated */
|
|
43
|
+
fileCount: number;
|
|
44
|
+
/** Number of domains in the model */
|
|
45
|
+
domainCount: number;
|
|
46
|
+
/** Number of bounded contexts in the model */
|
|
47
|
+
bcCount: number;
|
|
48
|
+
/** Validation errors */
|
|
49
|
+
errors: ValidationDiagnostic[];
|
|
50
|
+
/** Validation warnings */
|
|
51
|
+
warnings: ValidationDiagnostic[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Options for validation.
|
|
56
|
+
*/
|
|
57
|
+
export interface ValidationOptions {
|
|
58
|
+
/** Workspace directory (defaults to file's directory) */
|
|
59
|
+
workspaceDir?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Convert Langium diagnostic to ValidationDiagnostic.
|
|
64
|
+
*/
|
|
65
|
+
function toValidationDiagnostic(
|
|
66
|
+
diagnostic: { severity?: number; message: string; range: { start: { line: number; character: number } } },
|
|
67
|
+
file: string
|
|
68
|
+
): ValidationDiagnostic {
|
|
69
|
+
return {
|
|
70
|
+
severity: diagnostic.severity ?? 1,
|
|
71
|
+
message: diagnostic.message,
|
|
72
|
+
file,
|
|
73
|
+
line: diagnostic.range.start.line + 1,
|
|
74
|
+
column: diagnostic.range.start.character + 1,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Validates a DomainLang model file and all its imports.
|
|
80
|
+
*
|
|
81
|
+
* Uses the LSP infrastructure to:
|
|
82
|
+
* - Initialize the workspace
|
|
83
|
+
* - Resolve and load imports
|
|
84
|
+
* - Build and validate all documents
|
|
85
|
+
*
|
|
86
|
+
* @param filePath - Path to the entry .dlang file
|
|
87
|
+
* @param options - Validation options
|
|
88
|
+
* @returns Validation result with errors, warnings, and model statistics
|
|
89
|
+
* @throws Error if file doesn't exist or has invalid extension
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```typescript
|
|
93
|
+
* import { validateFile } from '@domainlang/language/sdk';
|
|
94
|
+
*
|
|
95
|
+
* const result = await validateFile('./index.dlang');
|
|
96
|
+
*
|
|
97
|
+
* if (!result.valid) {
|
|
98
|
+
* for (const err of result.errors) {
|
|
99
|
+
* console.error(`${err.file}:${err.line}:${err.column}: ${err.message}`);
|
|
100
|
+
* }
|
|
101
|
+
* process.exit(1);
|
|
102
|
+
* }
|
|
103
|
+
*
|
|
104
|
+
* console.log(`✓ Validated ${result.fileCount} files`);
|
|
105
|
+
* console.log(` ${result.domainCount} domains, ${result.bcCount} bounded contexts`);
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
export async function validateFile(
|
|
109
|
+
filePath: string,
|
|
110
|
+
options: ValidationOptions = {}
|
|
111
|
+
): Promise<ValidationResult> {
|
|
112
|
+
// Resolve absolute path
|
|
113
|
+
const absolutePath = resolve(filePath);
|
|
114
|
+
|
|
115
|
+
// Check file exists
|
|
116
|
+
if (!existsSync(absolutePath)) {
|
|
117
|
+
throw new Error(`File not found: ${filePath}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Create services with workspace support
|
|
121
|
+
const servicesObj = createDomainLangServices(NodeFileSystem);
|
|
122
|
+
const shared = servicesObj.shared;
|
|
123
|
+
const services = servicesObj.DomainLang;
|
|
124
|
+
|
|
125
|
+
// Check file extension
|
|
126
|
+
const extensions = services.LanguageMetaData.fileExtensions;
|
|
127
|
+
if (!extensions.some(ext => absolutePath.endsWith(ext))) {
|
|
128
|
+
throw new Error(`Invalid file extension. Expected: ${extensions.join(', ')}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Initialize workspace with the specified directory or file's directory
|
|
132
|
+
const workspaceDir = options.workspaceDir ?? dirname(absolutePath);
|
|
133
|
+
const workspaceManager = services.imports.WorkspaceManager;
|
|
134
|
+
await workspaceManager.initialize(workspaceDir);
|
|
135
|
+
|
|
136
|
+
// Load and parse the document
|
|
137
|
+
const uri = URI.file(absolutePath);
|
|
138
|
+
const document = await shared.workspace.LangiumDocuments.getOrCreateDocument(uri);
|
|
139
|
+
|
|
140
|
+
// Build document initially without validation to load imports
|
|
141
|
+
await shared.workspace.DocumentBuilder.build([document], { validation: false });
|
|
142
|
+
|
|
143
|
+
// Load all imported documents via the import graph
|
|
144
|
+
const importResolver = services.imports.ImportResolver;
|
|
145
|
+
await ensureImportGraphFromDocument(
|
|
146
|
+
document,
|
|
147
|
+
shared.workspace.LangiumDocuments,
|
|
148
|
+
importResolver
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
// Build all documents with validation enabled
|
|
152
|
+
const allDocuments = Array.from(shared.workspace.LangiumDocuments.all);
|
|
153
|
+
await shared.workspace.DocumentBuilder.build(allDocuments, { validation: true });
|
|
154
|
+
|
|
155
|
+
// Collect diagnostics from the entry document
|
|
156
|
+
const diagnostics = document.diagnostics ?? [];
|
|
157
|
+
const errors: ValidationDiagnostic[] = [];
|
|
158
|
+
const warnings: ValidationDiagnostic[] = [];
|
|
159
|
+
|
|
160
|
+
for (const diagnostic of diagnostics) {
|
|
161
|
+
const validationDiag = toValidationDiagnostic(diagnostic, absolutePath);
|
|
162
|
+
if (diagnostic.severity === 1) {
|
|
163
|
+
errors.push(validationDiag);
|
|
164
|
+
} else if (diagnostic.severity === 2) {
|
|
165
|
+
warnings.push(validationDiag);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Count model elements across all documents
|
|
170
|
+
let domainCount = 0;
|
|
171
|
+
let bcCount = 0;
|
|
172
|
+
|
|
173
|
+
for (const doc of allDocuments) {
|
|
174
|
+
const model = doc.parseResult?.value;
|
|
175
|
+
if (isModel(model)) {
|
|
176
|
+
for (const element of model.children ?? []) {
|
|
177
|
+
if (element.$type === 'Domain') {
|
|
178
|
+
domainCount++;
|
|
179
|
+
} else if (element.$type === 'BoundedContext') {
|
|
180
|
+
bcCount++;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
valid: errors.length === 0,
|
|
188
|
+
fileCount: allDocuments.length,
|
|
189
|
+
domainCount,
|
|
190
|
+
bcCount,
|
|
191
|
+
errors,
|
|
192
|
+
warnings,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Workspace validation result with diagnostics grouped by file.
|
|
198
|
+
*/
|
|
199
|
+
export interface WorkspaceValidationResult {
|
|
200
|
+
/** Whether the workspace is valid (no errors in any file) */
|
|
201
|
+
valid: boolean;
|
|
202
|
+
/** Number of files validated */
|
|
203
|
+
fileCount: number;
|
|
204
|
+
/** Number of domains across all files */
|
|
205
|
+
domainCount: number;
|
|
206
|
+
/** Number of bounded contexts across all files */
|
|
207
|
+
bcCount: number;
|
|
208
|
+
/** Validation errors grouped by file path */
|
|
209
|
+
errors: ValidationDiagnostic[];
|
|
210
|
+
/** Validation warnings grouped by file path */
|
|
211
|
+
warnings: ValidationDiagnostic[];
|
|
212
|
+
/** Total number of diagnostics across all files */
|
|
213
|
+
totalDiagnostics: number;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Validates an entire DomainLang workspace.
|
|
218
|
+
*
|
|
219
|
+
* Uses the LSP infrastructure to:
|
|
220
|
+
* - Initialize the workspace from a directory containing model.yaml
|
|
221
|
+
* - Load the entry file (from manifest or default index.dlang)
|
|
222
|
+
* - Resolve and load all imports
|
|
223
|
+
* - Build and validate all documents in the workspace
|
|
224
|
+
* - Collect diagnostics from ALL documents (like VS Code Problems pane)
|
|
225
|
+
*
|
|
226
|
+
* @param workspaceDir - Path to the workspace directory (containing model.yaml)
|
|
227
|
+
* @returns Validation result with diagnostics from all files
|
|
228
|
+
* @throws Error if workspace directory doesn't exist or cannot be loaded
|
|
229
|
+
*
|
|
230
|
+
* @example
|
|
231
|
+
* ```typescript
|
|
232
|
+
* import { validateWorkspace } from '@domainlang/language/sdk';
|
|
233
|
+
*
|
|
234
|
+
* const result = await validateWorkspace('./my-workspace');
|
|
235
|
+
*
|
|
236
|
+
* if (!result.valid) {
|
|
237
|
+
* console.error(`Found ${result.errors.length} errors in ${result.fileCount} files`);
|
|
238
|
+
*
|
|
239
|
+
* for (const err of result.errors) {
|
|
240
|
+
* console.error(`${err.file}:${err.line}:${err.column}: ${err.message}`);
|
|
241
|
+
* }
|
|
242
|
+
* process.exit(1);
|
|
243
|
+
* }
|
|
244
|
+
*
|
|
245
|
+
* console.log(`✓ Validated ${result.fileCount} files`);
|
|
246
|
+
* console.log(` ${result.domainCount} domains, ${result.bcCount} bounded contexts`);
|
|
247
|
+
* console.log(` 0 errors, ${result.warnings.length} warnings`);
|
|
248
|
+
* ```
|
|
249
|
+
*/
|
|
250
|
+
export async function validateWorkspace(
|
|
251
|
+
workspaceDir: string
|
|
252
|
+
): Promise<WorkspaceValidationResult> {
|
|
253
|
+
// Resolve absolute path
|
|
254
|
+
const absolutePath = resolve(workspaceDir);
|
|
255
|
+
|
|
256
|
+
// Check directory exists
|
|
257
|
+
if (!existsSync(absolutePath)) {
|
|
258
|
+
throw new Error(`Workspace directory not found: ${workspaceDir}`);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Create services with workspace support
|
|
262
|
+
const servicesObj = createDomainLangServices(NodeFileSystem);
|
|
263
|
+
const shared = servicesObj.shared;
|
|
264
|
+
const services = servicesObj.DomainLang;
|
|
265
|
+
const workspaceManager = services.imports.WorkspaceManager;
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
// Initialize workspace - this will find and load model.yaml
|
|
269
|
+
await workspaceManager.initialize(absolutePath);
|
|
270
|
+
} catch (error) {
|
|
271
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
272
|
+
throw new Error(`Failed to initialize workspace at ${workspaceDir}: ${message}`);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Get the manifest to find the entry file
|
|
276
|
+
const manifest = await workspaceManager.getManifest();
|
|
277
|
+
let entryFile = 'index.dlang';
|
|
278
|
+
|
|
279
|
+
if (manifest?.model?.entry) {
|
|
280
|
+
entryFile = manifest.model.entry;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const entryPath = join(absolutePath, entryFile);
|
|
284
|
+
|
|
285
|
+
// Check if entry file exists
|
|
286
|
+
if (!existsSync(entryPath)) {
|
|
287
|
+
throw new Error(
|
|
288
|
+
`Entry file not found: ${entryFile}\n` +
|
|
289
|
+
`Expected at: ${entryPath}\n` +
|
|
290
|
+
(manifest ? `Specified in manifest` : `Using default entry file`)
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Load and parse the entry document
|
|
295
|
+
const uri = URI.file(entryPath);
|
|
296
|
+
const document = await shared.workspace.LangiumDocuments.getOrCreateDocument(uri);
|
|
297
|
+
|
|
298
|
+
// Build document initially without validation to load imports
|
|
299
|
+
await shared.workspace.DocumentBuilder.build([document], { validation: false });
|
|
300
|
+
|
|
301
|
+
// Load all imported documents via the import graph
|
|
302
|
+
const importResolver = services.imports.ImportResolver;
|
|
303
|
+
await ensureImportGraphFromDocument(
|
|
304
|
+
document,
|
|
305
|
+
shared.workspace.LangiumDocuments,
|
|
306
|
+
importResolver
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
// Build all documents with validation enabled
|
|
310
|
+
const allDocuments = Array.from(shared.workspace.LangiumDocuments.all);
|
|
311
|
+
await shared.workspace.DocumentBuilder.build(allDocuments, { validation: true });
|
|
312
|
+
|
|
313
|
+
// Collect diagnostics from ALL documents (not just entry)
|
|
314
|
+
const errors: ValidationDiagnostic[] = [];
|
|
315
|
+
const warnings: ValidationDiagnostic[] = [];
|
|
316
|
+
|
|
317
|
+
for (const doc of allDocuments) {
|
|
318
|
+
const diagnostics = doc.diagnostics ?? [];
|
|
319
|
+
const docPath = doc.uri.fsPath;
|
|
320
|
+
|
|
321
|
+
for (const diagnostic of diagnostics) {
|
|
322
|
+
const validationDiag = toValidationDiagnostic(diagnostic, docPath);
|
|
323
|
+
|
|
324
|
+
if (diagnostic.severity === 1) {
|
|
325
|
+
errors.push(validationDiag);
|
|
326
|
+
} else if (diagnostic.severity === 2) {
|
|
327
|
+
warnings.push(validationDiag);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Count model elements across all documents
|
|
333
|
+
let domainCount = 0;
|
|
334
|
+
let bcCount = 0;
|
|
335
|
+
|
|
336
|
+
for (const doc of allDocuments) {
|
|
337
|
+
const model = doc.parseResult?.value;
|
|
338
|
+
if (isModel(model)) {
|
|
339
|
+
for (const element of model.children ?? []) {
|
|
340
|
+
if (element.$type === 'Domain') {
|
|
341
|
+
domainCount++;
|
|
342
|
+
} else if (element.$type === 'BoundedContext') {
|
|
343
|
+
bcCount++;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return {
|
|
350
|
+
valid: errors.length === 0,
|
|
351
|
+
fileCount: allDocuments.length,
|
|
352
|
+
domainCount,
|
|
353
|
+
bcCount,
|
|
354
|
+
errors,
|
|
355
|
+
warnings,
|
|
356
|
+
totalDiagnostics: errors.length + warnings.length,
|
|
357
|
+
};
|
|
358
|
+
}
|