@domainlang/language 0.5.2 → 0.6.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/domain-lang-module.js +3 -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 +102 -0
- package/out/lsp/domain-lang-index-manager.js +221 -0
- package/out/lsp/domain-lang-index-manager.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 +51 -9
- package/out/lsp/domain-lang-workspace-manager.js +86 -63
- 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 +109 -17
- package/out/main.js.map +1 -1
- package/out/services/import-resolver.d.ts +16 -2
- package/out/services/import-resolver.js +37 -11
- 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/import.d.ts +1 -2
- package/out/validation/import.js +33 -20
- package/out/validation/import.js.map +1 -1
- package/package.json +1 -1
- package/src/domain-lang-module.ts +4 -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 +256 -0
- package/src/lsp/domain-lang-scope.ts +29 -17
- package/src/lsp/domain-lang-workspace-manager.ts +89 -66
- 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 +127 -16
- package/src/services/import-resolver.ts +39 -11
- 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/import.ts +32 -22
- 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,256 @@
|
|
|
1
|
+
import type { LangiumDocument, LangiumSharedCoreServices, URI } from 'langium';
|
|
2
|
+
import { DefaultIndexManager, DocumentState } from 'langium';
|
|
3
|
+
import { CancellationToken } from 'vscode-jsonrpc';
|
|
4
|
+
import { resolveImportPath } from '../utils/import-utils.js';
|
|
5
|
+
import type { Model } from '../generated/ast.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Custom IndexManager that extends Langium's default to:
|
|
9
|
+
* 1. Automatically load imported documents during indexing
|
|
10
|
+
* 2. Track import dependencies for cross-file revalidation
|
|
11
|
+
*
|
|
12
|
+
* **Why this exists:**
|
|
13
|
+
* Langium's `DefaultIndexManager.isAffected()` only checks cross-references
|
|
14
|
+
* (elements declared with `[Type]` grammar syntax). DomainLang's imports use
|
|
15
|
+
* string literals (`import "path"`), which are not cross-references.
|
|
16
|
+
*
|
|
17
|
+
* **How it works:**
|
|
18
|
+
* - When a document is indexed, we ensure all its imports are also loaded
|
|
19
|
+
* - Maintains a reverse dependency graph: importedUri → Set<importingUri>
|
|
20
|
+
* - Overrides `isAffected()` to also check this graph
|
|
21
|
+
* - This integrates with Langium's native `DocumentBuilder.update()` flow
|
|
22
|
+
*
|
|
23
|
+
* **Integration with Langium:**
|
|
24
|
+
* This approach is idiomatic because:
|
|
25
|
+
* 1. `updateContent()` is called for EVERY document during build
|
|
26
|
+
* 2. We load imports during indexing, BEFORE linking/validation
|
|
27
|
+
* 3. `DocumentBuilder.shouldRelink()` calls `IndexManager.isAffected()`
|
|
28
|
+
* 4. No need for separate lifecycle service - this IS the central place
|
|
29
|
+
*/
|
|
30
|
+
export class DomainLangIndexManager extends DefaultIndexManager {
|
|
31
|
+
/**
|
|
32
|
+
* Reverse dependency graph: maps a document URI to all documents that import it.
|
|
33
|
+
* Key: imported document URI (string)
|
|
34
|
+
* Value: Set of URIs of documents that import the key document
|
|
35
|
+
*/
|
|
36
|
+
private readonly importDependencies = new Map<string, Set<string>>();
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Tracks documents that have had their imports loaded to avoid redundant work.
|
|
40
|
+
* Cleared on workspace config changes.
|
|
41
|
+
*/
|
|
42
|
+
private readonly importsLoaded = new Set<string>();
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Reference to shared services for accessing LangiumDocuments.
|
|
46
|
+
*/
|
|
47
|
+
private readonly sharedServices: LangiumSharedCoreServices;
|
|
48
|
+
|
|
49
|
+
constructor(services: LangiumSharedCoreServices) {
|
|
50
|
+
super(services);
|
|
51
|
+
this.sharedServices = services;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Extends the default content update to:
|
|
56
|
+
* 1. Ensure all imported documents are loaded
|
|
57
|
+
* 2. Track import dependencies for change propagation
|
|
58
|
+
*
|
|
59
|
+
* Called by Langium during the IndexedContent build phase.
|
|
60
|
+
* This is BEFORE linking/validation, so imports are available for resolution.
|
|
61
|
+
*/
|
|
62
|
+
override async updateContent(document: LangiumDocument, cancelToken = CancellationToken.None): Promise<void> {
|
|
63
|
+
// First, do the standard content indexing
|
|
64
|
+
await super.updateContent(document, cancelToken);
|
|
65
|
+
|
|
66
|
+
// Then, ensure imports are loaded and track dependencies
|
|
67
|
+
await this.ensureImportsLoaded(document);
|
|
68
|
+
await this.trackImportDependencies(document);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Extends the default remove to also clean up import dependencies.
|
|
73
|
+
*/
|
|
74
|
+
override remove(uri: URI): void {
|
|
75
|
+
super.remove(uri);
|
|
76
|
+
const uriString = uri.toString();
|
|
77
|
+
this.removeImportDependencies(uriString);
|
|
78
|
+
this.importsLoaded.delete(uriString);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Extends the default content removal to also clean up import dependencies.
|
|
83
|
+
*/
|
|
84
|
+
override removeContent(uri: URI): void {
|
|
85
|
+
super.removeContent(uri);
|
|
86
|
+
const uriString = uri.toString();
|
|
87
|
+
this.removeImportDependencies(uriString);
|
|
88
|
+
this.importsLoaded.delete(uriString);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Extends `isAffected` to also check import dependencies.
|
|
93
|
+
*
|
|
94
|
+
* A document is affected if:
|
|
95
|
+
* 1. It has cross-references to any changed document (default Langium behavior)
|
|
96
|
+
* 2. It imports any of the changed documents (our extension)
|
|
97
|
+
*/
|
|
98
|
+
override isAffected(document: LangiumDocument, changedUris: Set<string>): boolean {
|
|
99
|
+
// First check Langium's default: cross-references
|
|
100
|
+
if (super.isAffected(document, changedUris)) {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Then check our import dependencies
|
|
105
|
+
const docUri = document.uri.toString();
|
|
106
|
+
for (const changedUri of changedUris) {
|
|
107
|
+
const dependents = this.importDependencies.get(changedUri);
|
|
108
|
+
if (dependents?.has(docUri)) {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Tracks import dependencies for a document.
|
|
118
|
+
* For each import in the document, records that the imported URI is depended upon.
|
|
119
|
+
*/
|
|
120
|
+
private async trackImportDependencies(document: LangiumDocument): Promise<void> {
|
|
121
|
+
const importingUri = document.uri.toString();
|
|
122
|
+
|
|
123
|
+
// First, remove old dependencies from this document
|
|
124
|
+
// (in case imports changed)
|
|
125
|
+
this.removeDocumentFromDependencies(importingUri);
|
|
126
|
+
|
|
127
|
+
// Skip if document isn't ready (no parse result)
|
|
128
|
+
if (document.state < DocumentState.Parsed) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const model = document.parseResult.value as unknown as Model;
|
|
133
|
+
if (!model.imports) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
for (const imp of model.imports) {
|
|
138
|
+
if (!imp.uri) continue;
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
const resolvedUri = await resolveImportPath(document, imp.uri);
|
|
142
|
+
const importedUri = resolvedUri.toString();
|
|
143
|
+
|
|
144
|
+
// Add to reverse dependency graph: importedUri → importingUri
|
|
145
|
+
let dependents = this.importDependencies.get(importedUri);
|
|
146
|
+
if (!dependents) {
|
|
147
|
+
dependents = new Set();
|
|
148
|
+
this.importDependencies.set(importedUri, dependents);
|
|
149
|
+
}
|
|
150
|
+
dependents.add(importingUri);
|
|
151
|
+
} catch {
|
|
152
|
+
// Import resolution failed - validation will report the error
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Ensures all imported documents are loaded and available.
|
|
159
|
+
* This is called during indexing, BEFORE linking/validation,
|
|
160
|
+
* so that cross-file references can be resolved.
|
|
161
|
+
*
|
|
162
|
+
* Works for both workspace files and standalone files.
|
|
163
|
+
*/
|
|
164
|
+
private async ensureImportsLoaded(document: LangiumDocument): Promise<void> {
|
|
165
|
+
const uriString = document.uri.toString();
|
|
166
|
+
|
|
167
|
+
// Skip if already processed (avoid redundant work and infinite loops)
|
|
168
|
+
if (this.importsLoaded.has(uriString)) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
this.importsLoaded.add(uriString);
|
|
172
|
+
|
|
173
|
+
// Skip if document isn't ready (no parse result)
|
|
174
|
+
if (document.state < DocumentState.Parsed) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const model = document.parseResult.value as unknown as Model;
|
|
179
|
+
if (!model.imports || model.imports.length === 0) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const langiumDocuments = this.sharedServices.workspace.LangiumDocuments;
|
|
184
|
+
const documentBuilder = this.sharedServices.workspace.DocumentBuilder;
|
|
185
|
+
const newDocs: LangiumDocument[] = [];
|
|
186
|
+
|
|
187
|
+
for (const imp of model.imports) {
|
|
188
|
+
if (!imp.uri) continue;
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
const resolvedUri = await resolveImportPath(document, imp.uri);
|
|
192
|
+
const importedUriString = resolvedUri.toString();
|
|
193
|
+
|
|
194
|
+
// Skip if already loaded
|
|
195
|
+
if (this.importsLoaded.has(importedUriString)) {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Load or create the imported document
|
|
200
|
+
const importedDoc = await langiumDocuments.getOrCreateDocument(resolvedUri);
|
|
201
|
+
|
|
202
|
+
// If document is new (not yet indexed), add to batch
|
|
203
|
+
if (importedDoc.state < DocumentState.IndexedContent) {
|
|
204
|
+
newDocs.push(importedDoc);
|
|
205
|
+
}
|
|
206
|
+
} catch {
|
|
207
|
+
// Import resolution failed - validation will report the error
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Build any newly discovered documents
|
|
212
|
+
// This triggers indexing which will recursively load their imports
|
|
213
|
+
if (newDocs.length > 0) {
|
|
214
|
+
await documentBuilder.build(newDocs, { validation: true });
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Removes a document from the import dependencies graph entirely.
|
|
220
|
+
* Called when a document is deleted.
|
|
221
|
+
*/
|
|
222
|
+
private removeImportDependencies(uri: string): void {
|
|
223
|
+
// Remove as an imported document
|
|
224
|
+
this.importDependencies.delete(uri);
|
|
225
|
+
|
|
226
|
+
// Remove from all dependency sets (as an importer)
|
|
227
|
+
this.removeDocumentFromDependencies(uri);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Removes a document from all dependency sets.
|
|
232
|
+
* Called when a document's imports change or it's deleted.
|
|
233
|
+
*/
|
|
234
|
+
private removeDocumentFromDependencies(uri: string): void {
|
|
235
|
+
for (const deps of this.importDependencies.values()) {
|
|
236
|
+
deps.delete(uri);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Clears all import-related caches.
|
|
242
|
+
* Call this when workspace configuration changes.
|
|
243
|
+
*/
|
|
244
|
+
clearImportDependencies(): void {
|
|
245
|
+
this.importDependencies.clear();
|
|
246
|
+
this.importsLoaded.clear();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Marks a document as needing import re-loading.
|
|
251
|
+
* Called when a document's content changes.
|
|
252
|
+
*/
|
|
253
|
+
markForReprocessing(uri: string): void {
|
|
254
|
+
this.importsLoaded.delete(uri);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
@@ -48,22 +48,28 @@ export class DomainLangScopeComputation extends DefaultScopeComputation {
|
|
|
48
48
|
* @returns A promise resolving to an array of AstNodeDescription
|
|
49
49
|
*/
|
|
50
50
|
override async collectExportedSymbols(document: LangiumDocument, cancelToken = CancellationToken.None): Promise<AstNodeDescription[]> {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
51
|
+
try {
|
|
52
|
+
const descr: AstNodeDescription[] = [];
|
|
53
|
+
for (const modelNode of AstUtils.streamAllContents(document.parseResult.value)) {
|
|
54
|
+
await interruptAndCheck(cancelToken);
|
|
55
|
+
if (isType(modelNode)) {
|
|
56
|
+
let name = this.nameProvider.getName(modelNode);
|
|
57
|
+
if (!name) {
|
|
58
|
+
// Defensive: skip unnamed types
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (isNamespaceDeclaration(modelNode.$container)) {
|
|
62
|
+
name = this.qualifiedNameProvider.getQualifiedName(modelNode.$container, name);
|
|
63
|
+
}
|
|
64
|
+
descr.push(this.descriptions.createDescription(modelNode, name, document));
|
|
62
65
|
}
|
|
63
|
-
descr.push(this.descriptions.createDescription(modelNode, name, document));
|
|
64
66
|
}
|
|
67
|
+
return descr;
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error('Error in collectExportedSymbols:', error);
|
|
70
|
+
// Return empty array on error to prevent crash
|
|
71
|
+
return [];
|
|
65
72
|
}
|
|
66
|
-
return descr;
|
|
67
73
|
}
|
|
68
74
|
|
|
69
75
|
/**
|
|
@@ -73,10 +79,16 @@ export class DomainLangScopeComputation extends DefaultScopeComputation {
|
|
|
73
79
|
* @returns A promise resolving to a LocalSymbols map
|
|
74
80
|
*/
|
|
75
81
|
override async collectLocalSymbols(document: LangiumDocument, cancelToken = CancellationToken.None): Promise<LocalSymbols> {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
82
|
+
try {
|
|
83
|
+
const model = document.parseResult.value as Model;
|
|
84
|
+
const scopes = new MultiMap<AstNode, AstNodeDescription>();
|
|
85
|
+
await this.processContainer(model, scopes, document, cancelToken);
|
|
86
|
+
return scopes;
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error('Error in collectLocalSymbols:', error);
|
|
89
|
+
// Return empty multimap on error to prevent crash
|
|
90
|
+
return new MultiMap<AstNode, AstNodeDescription>();
|
|
91
|
+
}
|
|
80
92
|
}
|
|
81
93
|
|
|
82
94
|
/**
|
|
@@ -1,21 +1,60 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import YAML from 'yaml';
|
|
3
1
|
import { DefaultWorkspaceManager, URI, UriUtils, type FileSystemNode, type LangiumDocument, type LangiumSharedCoreServices, type WorkspaceFolder } from 'langium';
|
|
4
2
|
import type { CancellationToken } from 'vscode-languageserver-protocol';
|
|
5
3
|
import { ensureImportGraphFromDocument } from '../utils/import-utils.js';
|
|
4
|
+
import { findManifestsInDirectories } from '../utils/manifest-utils.js';
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* Langium WorkspaceManager override implementing manifest-centric import loading per PRS-010.
|
|
9
8
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* -
|
|
14
|
-
* -
|
|
9
|
+
* **Three Operational Modes:**
|
|
10
|
+
*
|
|
11
|
+
* **Mode A (Pure Workspace with model.yaml):**
|
|
12
|
+
* - model.yaml exists at workspace root
|
|
13
|
+
* - Loads entry file (default: index.dlang, or custom via model.entry)
|
|
14
|
+
* - Pre-builds entry and follows import graph
|
|
15
|
+
* - All imported documents built to Validated state before workspace ready
|
|
16
|
+
* - LSP features have immediate access to complete reference information
|
|
17
|
+
*
|
|
18
|
+
* **Mode B (Pure Standalone files):**
|
|
19
|
+
* - No model.yaml anywhere in workspace
|
|
20
|
+
* - No pre-loading of .dlang files during workspace scan
|
|
21
|
+
* - Documents loaded on-demand when user opens them
|
|
22
|
+
* - Imports resolved lazily via ImportResolver
|
|
23
|
+
* - Each document built individually when opened
|
|
24
|
+
* - Works with relative imports only (no path aliases or external deps)
|
|
25
|
+
*
|
|
26
|
+
* **Mode C (Mixed - Standalone + Module folders):**
|
|
27
|
+
* - Workspace contains both standalone .dlang files AND folders with model.yaml
|
|
28
|
+
* - Each model.yaml folder treated as a module/package:
|
|
29
|
+
* - Module entry + import graph pre-loaded
|
|
30
|
+
* - Path aliases and external deps work within module
|
|
31
|
+
* - Standalone files outside modules loaded on-demand
|
|
32
|
+
* - Example structure:
|
|
33
|
+
* ```
|
|
34
|
+
* workspace/
|
|
35
|
+
* ├── standalone.dlang ← Mode B (on-demand)
|
|
36
|
+
* ├── core/
|
|
37
|
+
* │ ├── model.yaml ← Module root
|
|
38
|
+
* │ ├── index.dlang ← Pre-loaded
|
|
39
|
+
* │ └── domains/
|
|
40
|
+
* │ └── sales.dlang ← Pre-loaded via imports
|
|
41
|
+
* └── util.dlang ← Mode B (on-demand)
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* **Performance Characteristics:**
|
|
45
|
+
* - Mode A/C modules: Slower initial load, instant LSP features afterward
|
|
46
|
+
* - Mode B/C standalone: Instant workspace init, per-file build on open
|
|
47
|
+
* - All modes cache import resolution for subsequent access
|
|
48
|
+
*
|
|
49
|
+
* **Never performs network fetches** - relies on cached dependencies/lock files.
|
|
50
|
+
* Missing cache produces diagnostics upstream via ImportValidator.
|
|
15
51
|
*/
|
|
16
52
|
export class DomainLangWorkspaceManager extends DefaultWorkspaceManager {
|
|
53
|
+
private readonly sharedServices: LangiumSharedCoreServices;
|
|
54
|
+
|
|
17
55
|
constructor(services: LangiumSharedCoreServices) {
|
|
18
56
|
super(services);
|
|
57
|
+
this.sharedServices = services;
|
|
19
58
|
}
|
|
20
59
|
|
|
21
60
|
override shouldIncludeEntry(entry: FileSystemNode): boolean {
|
|
@@ -32,73 +71,57 @@ export class DomainLangWorkspaceManager extends DefaultWorkspaceManager {
|
|
|
32
71
|
}
|
|
33
72
|
|
|
34
73
|
protected override async loadAdditionalDocuments(folders: WorkspaceFolder[], collector: (document: LangiumDocument) => void): Promise<void> {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const entryUri = URI.file(manifestInfo.entryPath);
|
|
41
|
-
const entryDoc = await this.langiumDocuments.getOrCreateDocument(entryUri);
|
|
42
|
-
collector(entryDoc);
|
|
43
|
-
|
|
44
|
-
const uris = await ensureImportGraphFromDocument(entryDoc, this.langiumDocuments);
|
|
45
|
-
for (const uriString of uris) {
|
|
46
|
-
const uri = URI.parse(uriString);
|
|
47
|
-
const doc = await this.langiumDocuments.getOrCreateDocument(uri);
|
|
48
|
-
collector(doc);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
private async findManifestInFolders(folders: WorkspaceFolder[]): Promise<{ manifestPath: string; entryPath: string } | undefined> {
|
|
53
|
-
for (const folder of folders) {
|
|
54
|
-
const manifestPath = await this.findNearestManifest(folder.uri);
|
|
55
|
-
if (manifestPath) {
|
|
56
|
-
const entry = await this.readEntryFromManifest(manifestPath) ?? 'index.dlang';
|
|
57
|
-
const entryPath = path.resolve(path.dirname(manifestPath), entry);
|
|
58
|
-
return { manifestPath, entryPath };
|
|
59
|
-
}
|
|
74
|
+
// Find ALL model.yaml files in workspace (supports mixed mode)
|
|
75
|
+
const manifestInfos = await this.findAllManifestsInFolders(folders);
|
|
76
|
+
|
|
77
|
+
if (manifestInfos.length === 0) {
|
|
78
|
+
return; // Pure Mode B: no manifests, all files loaded on-demand
|
|
60
79
|
}
|
|
61
|
-
return undefined;
|
|
62
|
-
}
|
|
63
80
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
81
|
+
// Mode A or Mode C: Load each module's entry + import graph
|
|
82
|
+
for (const manifestInfo of manifestInfos) {
|
|
83
|
+
try {
|
|
84
|
+
const entryUri = URI.file(manifestInfo.entryPath);
|
|
85
|
+
const entryDoc = await this.langiumDocuments.getOrCreateDocument(entryUri);
|
|
86
|
+
collector(entryDoc);
|
|
67
87
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
88
|
+
// Build entry document first to ensure it's ready for import resolution
|
|
89
|
+
await this.sharedServices.workspace.DocumentBuilder.build([entryDoc], {
|
|
90
|
+
validation: true
|
|
91
|
+
});
|
|
73
92
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
93
|
+
const uris = await ensureImportGraphFromDocument(entryDoc, this.langiumDocuments);
|
|
94
|
+
const importedDocs: LangiumDocument[] = [];
|
|
95
|
+
for (const uriString of uris) {
|
|
96
|
+
const uri = URI.parse(uriString);
|
|
97
|
+
const doc = await this.langiumDocuments.getOrCreateDocument(uri);
|
|
98
|
+
collector(doc);
|
|
99
|
+
importedDocs.push(doc);
|
|
100
|
+
}
|
|
77
101
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
102
|
+
// Build all imported documents in batch for performance
|
|
103
|
+
if (importedDocs.length > 0) {
|
|
104
|
+
await this.sharedServices.workspace.DocumentBuilder.build(importedDocs, {
|
|
105
|
+
validation: true
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
} catch (error) {
|
|
109
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
110
|
+
console.error(`Failed to load import graph from ${manifestInfo.manifestPath}: ${message}`);
|
|
111
|
+
// Continue with other modules - partial failure is acceptable
|
|
81
112
|
}
|
|
82
|
-
current = parent;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
private async readEntryFromManifest(manifestPath: string): Promise<string | undefined> {
|
|
87
|
-
try {
|
|
88
|
-
const content = await this.fileSystemProvider.readFile(URI.file(manifestPath));
|
|
89
|
-
const manifest = (YAML.parse(content) ?? {}) as { model?: { entry?: string } };
|
|
90
|
-
return manifest.model?.entry;
|
|
91
|
-
} catch {
|
|
92
|
-
return undefined;
|
|
93
113
|
}
|
|
94
114
|
}
|
|
95
115
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
116
|
+
/**
|
|
117
|
+
* Finds ALL model.yaml files in the workspace.
|
|
118
|
+
* Delegates to shared manifest utilities.
|
|
119
|
+
*
|
|
120
|
+
* @param folders - Workspace folders to search
|
|
121
|
+
* @returns Array of manifest info (one per model.yaml found)
|
|
122
|
+
*/
|
|
123
|
+
private async findAllManifestsInFolders(folders: WorkspaceFolder[]): Promise<Array<{ manifestPath: string; entryPath: string }>> {
|
|
124
|
+
const directories = folders.map(f => URI.parse(f.uri).fsPath);
|
|
125
|
+
return findManifestsInDirectories(directories);
|
|
103
126
|
}
|
|
104
127
|
}
|