@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,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DomainLang Scope Provider
|
|
3
|
+
*
|
|
4
|
+
* Implements import-based scoping for the DomainLang DSL.
|
|
5
|
+
*
|
|
6
|
+
* **Key Concept:**
|
|
7
|
+
* Unlike languages with global namespaces, DomainLang enforces strict import-based scoping:
|
|
8
|
+
* - Elements are only visible if they are defined in the current document OR explicitly imported
|
|
9
|
+
* - The global scope is restricted to imported documents only
|
|
10
|
+
* - Transitive imports do NOT provide scope (only direct imports)
|
|
11
|
+
*
|
|
12
|
+
* **Why this matters:**
|
|
13
|
+
* Without this, Langium's DefaultScopeProvider would make ALL indexed documents visible
|
|
14
|
+
* in the global scope, which would:
|
|
15
|
+
* 1. Allow referencing elements that haven't been imported
|
|
16
|
+
* 2. Make the import system meaningless
|
|
17
|
+
* 3. Create confusion about dependencies between files
|
|
18
|
+
*
|
|
19
|
+
* @see https://langium.org/docs/recipes/scoping/ for Langium scoping patterns
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import type {
|
|
23
|
+
AstNodeDescription,
|
|
24
|
+
LangiumDocument,
|
|
25
|
+
ReferenceInfo,
|
|
26
|
+
Scope,
|
|
27
|
+
Stream
|
|
28
|
+
} from 'langium';
|
|
29
|
+
import {
|
|
30
|
+
AstUtils,
|
|
31
|
+
DefaultScopeProvider,
|
|
32
|
+
EMPTY_SCOPE,
|
|
33
|
+
MapScope
|
|
34
|
+
} from 'langium';
|
|
35
|
+
import type { DomainLangServices } from '../domain-lang-module.js';
|
|
36
|
+
import type { DomainLangIndexManager } from './domain-lang-index-manager.js';
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Custom scope provider that restricts cross-file references to imported documents only.
|
|
40
|
+
*
|
|
41
|
+
* Extends Langium's DefaultScopeProvider to override the global scope computation.
|
|
42
|
+
*/
|
|
43
|
+
export class DomainLangScopeProvider extends DefaultScopeProvider {
|
|
44
|
+
/**
|
|
45
|
+
* Reference to IndexManager for getting resolved imports.
|
|
46
|
+
*/
|
|
47
|
+
private readonly domainLangIndexManager: DomainLangIndexManager;
|
|
48
|
+
|
|
49
|
+
constructor(services: DomainLangServices) {
|
|
50
|
+
super(services);
|
|
51
|
+
this.domainLangIndexManager = services.shared.workspace.IndexManager as DomainLangIndexManager;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Override getGlobalScope to restrict it to imported documents only.
|
|
56
|
+
*
|
|
57
|
+
* The default Langium behavior includes ALL documents in the workspace.
|
|
58
|
+
* We restrict this to:
|
|
59
|
+
* 1. The current document's own exported symbols
|
|
60
|
+
* 2. Exported symbols from directly imported documents
|
|
61
|
+
*
|
|
62
|
+
* @param referenceType - The AST type being referenced
|
|
63
|
+
* @param context - Information about the reference
|
|
64
|
+
* @returns A scope containing only visible elements
|
|
65
|
+
*/
|
|
66
|
+
protected override getGlobalScope(referenceType: string, context: ReferenceInfo): Scope {
|
|
67
|
+
const document = AstUtils.getDocument(context.container);
|
|
68
|
+
if (!document) {
|
|
69
|
+
return EMPTY_SCOPE;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Get the set of URIs that are in scope for this document
|
|
73
|
+
const importedUris = this.getImportedDocumentUris(document);
|
|
74
|
+
|
|
75
|
+
// Filter the global index to only include descriptions from imported documents
|
|
76
|
+
const filteredDescriptions = this.filterDescriptionsByImports(
|
|
77
|
+
referenceType,
|
|
78
|
+
document,
|
|
79
|
+
importedUris
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
// Create a scope from the filtered descriptions
|
|
83
|
+
return new MapScope(filteredDescriptions);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Gets the set of document URIs that are directly imported by the given document.
|
|
88
|
+
*
|
|
89
|
+
* Uses the resolved imports tracked by DomainLangIndexManager during indexing.
|
|
90
|
+
* This ensures accurate resolution including path aliases.
|
|
91
|
+
*
|
|
92
|
+
* @param document - The document to get imports for
|
|
93
|
+
* @returns Set of imported document URIs (as strings)
|
|
94
|
+
*/
|
|
95
|
+
private getImportedDocumentUris(document: LangiumDocument): Set<string> {
|
|
96
|
+
const docUri = document.uri.toString();
|
|
97
|
+
|
|
98
|
+
// Get resolved imports from the index manager (tracked during indexing)
|
|
99
|
+
const resolvedImports = this.domainLangIndexManager.getResolvedImports(docUri);
|
|
100
|
+
|
|
101
|
+
// Always include the current document itself
|
|
102
|
+
const importedUris = new Set<string>([docUri]);
|
|
103
|
+
|
|
104
|
+
// Add all resolved import URIs
|
|
105
|
+
for (const resolvedUri of resolvedImports) {
|
|
106
|
+
importedUris.add(resolvedUri);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return importedUris;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Filters the global index to only include descriptions from imported documents.
|
|
114
|
+
*
|
|
115
|
+
* @param referenceType - The AST type being referenced
|
|
116
|
+
* @param currentDocument - The document making the reference
|
|
117
|
+
* @param importedUris - Set of URIs that are in scope
|
|
118
|
+
* @returns Stream of filtered descriptions
|
|
119
|
+
*/
|
|
120
|
+
private filterDescriptionsByImports(
|
|
121
|
+
referenceType: string,
|
|
122
|
+
currentDocument: LangiumDocument,
|
|
123
|
+
importedUris: Set<string>
|
|
124
|
+
): Stream<AstNodeDescription> {
|
|
125
|
+
// Get all descriptions of the reference type from the index
|
|
126
|
+
const allDescriptions = this.indexManager.allElements(referenceType);
|
|
127
|
+
|
|
128
|
+
// Filter to only those from imported documents
|
|
129
|
+
return allDescriptions.filter(desc => {
|
|
130
|
+
const descDocUri = desc.documentUri.toString();
|
|
131
|
+
return importedUris.has(descDocUri);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -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,62 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
1
2
|
import path from 'node:path';
|
|
2
|
-
import YAML from 'yaml';
|
|
3
3
|
import { DefaultWorkspaceManager, URI, UriUtils, type FileSystemNode, type LangiumDocument, type LangiumSharedCoreServices, type WorkspaceFolder } from 'langium';
|
|
4
4
|
import type { CancellationToken } from 'vscode-languageserver-protocol';
|
|
5
5
|
import { ensureImportGraphFromDocument } from '../utils/import-utils.js';
|
|
6
|
+
import { findManifestsInDirectories } from '../utils/manifest-utils.js';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Langium WorkspaceManager override implementing manifest-centric import loading per PRS-010.
|
|
9
10
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* -
|
|
14
|
-
* -
|
|
11
|
+
* **Three Operational Modes:**
|
|
12
|
+
*
|
|
13
|
+
* **Mode A (Pure Workspace with model.yaml):**
|
|
14
|
+
* - model.yaml exists at workspace root
|
|
15
|
+
* - Loads entry file (default: index.dlang, or custom via model.entry)
|
|
16
|
+
* - Pre-builds entry and follows import graph
|
|
17
|
+
* - All imported documents built to Validated state before workspace ready
|
|
18
|
+
* - LSP features have immediate access to complete reference information
|
|
19
|
+
*
|
|
20
|
+
* **Mode B (Pure Standalone files):**
|
|
21
|
+
* - No model.yaml anywhere in workspace
|
|
22
|
+
* - No pre-loading of .dlang files during workspace scan
|
|
23
|
+
* - Documents loaded on-demand when user opens them
|
|
24
|
+
* - Imports resolved lazily via ImportResolver
|
|
25
|
+
* - Each document built individually when opened
|
|
26
|
+
* - Works with relative imports only (no path aliases or external deps)
|
|
27
|
+
*
|
|
28
|
+
* **Mode C (Mixed - Standalone + Module folders):**
|
|
29
|
+
* - Workspace contains both standalone .dlang files AND folders with model.yaml
|
|
30
|
+
* - Each model.yaml folder treated as a module/package:
|
|
31
|
+
* - Module entry + import graph pre-loaded
|
|
32
|
+
* - Path aliases and external deps work within module
|
|
33
|
+
* - Standalone files outside modules loaded on-demand
|
|
34
|
+
* - Example structure:
|
|
35
|
+
* ```
|
|
36
|
+
* workspace/
|
|
37
|
+
* ├── standalone.dlang ← Mode B (on-demand)
|
|
38
|
+
* ├── core/
|
|
39
|
+
* │ ├── model.yaml ← Module root
|
|
40
|
+
* │ ├── index.dlang ← Pre-loaded
|
|
41
|
+
* │ └── domains/
|
|
42
|
+
* │ └── sales.dlang ← Pre-loaded via imports
|
|
43
|
+
* └── util.dlang ← Mode B (on-demand)
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* **Performance Characteristics:**
|
|
47
|
+
* - Mode A/C modules: Slower initial load, instant LSP features afterward
|
|
48
|
+
* - Mode B/C standalone: Instant workspace init, per-file build on open
|
|
49
|
+
* - All modes cache import resolution for subsequent access
|
|
50
|
+
*
|
|
51
|
+
* **Never performs network fetches** - relies on cached dependencies/lock files.
|
|
52
|
+
* Missing cache produces diagnostics upstream via ImportValidator.
|
|
15
53
|
*/
|
|
16
54
|
export class DomainLangWorkspaceManager extends DefaultWorkspaceManager {
|
|
55
|
+
private readonly sharedServices: LangiumSharedCoreServices;
|
|
56
|
+
|
|
17
57
|
constructor(services: LangiumSharedCoreServices) {
|
|
18
58
|
super(services);
|
|
59
|
+
this.sharedServices = services;
|
|
19
60
|
}
|
|
20
61
|
|
|
21
62
|
override shouldIncludeEntry(entry: FileSystemNode): boolean {
|
|
@@ -32,73 +73,180 @@ export class DomainLangWorkspaceManager extends DefaultWorkspaceManager {
|
|
|
32
73
|
}
|
|
33
74
|
|
|
34
75
|
protected override async loadAdditionalDocuments(folders: WorkspaceFolder[], collector: (document: LangiumDocument) => void): Promise<void> {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
76
|
+
// Find ALL model.yaml files in workspace (supports mixed mode)
|
|
77
|
+
const manifestInfos = await this.findAllManifestsInFolders(folders);
|
|
78
|
+
|
|
79
|
+
// Track directories covered by manifests to avoid loading their files as standalone
|
|
80
|
+
const moduleDirectories = new Set(
|
|
81
|
+
manifestInfos.map(m => path.dirname(m.manifestPath))
|
|
82
|
+
);
|
|
39
83
|
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
|
|
84
|
+
// Mode A or Mode C: Load each module's entry + import graph
|
|
85
|
+
for (const manifestInfo of manifestInfos) {
|
|
86
|
+
try {
|
|
87
|
+
const entryUri = URI.file(manifestInfo.entryPath);
|
|
88
|
+
const entryDoc = await this.langiumDocuments.getOrCreateDocument(entryUri);
|
|
89
|
+
collector(entryDoc);
|
|
43
90
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
91
|
+
// Build entry document first to ensure it's ready for import resolution
|
|
92
|
+
await this.sharedServices.workspace.DocumentBuilder.build([entryDoc], {
|
|
93
|
+
validation: true
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const uris = await ensureImportGraphFromDocument(entryDoc, this.langiumDocuments);
|
|
97
|
+
const importedDocs: LangiumDocument[] = [];
|
|
98
|
+
for (const uriString of uris) {
|
|
99
|
+
const uri = URI.parse(uriString);
|
|
100
|
+
const doc = await this.langiumDocuments.getOrCreateDocument(uri);
|
|
101
|
+
collector(doc);
|
|
102
|
+
importedDocs.push(doc);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Build all imported documents in batch for performance
|
|
106
|
+
if (importedDocs.length > 0) {
|
|
107
|
+
await this.sharedServices.workspace.DocumentBuilder.build(importedDocs, {
|
|
108
|
+
validation: true
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
} catch (error) {
|
|
112
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
113
|
+
console.error(`Failed to load import graph from ${manifestInfo.manifestPath}: ${message}`);
|
|
114
|
+
// Continue with other modules - partial failure is acceptable
|
|
115
|
+
}
|
|
49
116
|
}
|
|
117
|
+
|
|
118
|
+
// Load standalone .dlang files in workspace root folders
|
|
119
|
+
// These are files NOT covered by any module's import graph
|
|
120
|
+
await this.loadStandaloneFiles(folders, moduleDirectories, collector);
|
|
50
121
|
}
|
|
51
122
|
|
|
52
|
-
|
|
123
|
+
/**
|
|
124
|
+
* Loads standalone .dlang files from workspace folders recursively.
|
|
125
|
+
*
|
|
126
|
+
* Skips:
|
|
127
|
+
* - Module directories (directories with model.yaml) - loaded via import graph
|
|
128
|
+
* - `.dlang/cache` directory - package cache managed by CLI
|
|
129
|
+
*
|
|
130
|
+
* @param folders - Workspace folders to scan
|
|
131
|
+
* @param moduleDirectories - Set of directories containing model.yaml (to skip)
|
|
132
|
+
* @param collector - Document collector callback
|
|
133
|
+
*/
|
|
134
|
+
private async loadStandaloneFiles(
|
|
135
|
+
folders: WorkspaceFolder[],
|
|
136
|
+
moduleDirectories: Set<string>,
|
|
137
|
+
collector: (document: LangiumDocument) => void
|
|
138
|
+
): Promise<void> {
|
|
139
|
+
const standaloneDocs: LangiumDocument[] = [];
|
|
140
|
+
|
|
53
141
|
for (const folder of folders) {
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
142
|
+
const folderPath = URI.parse(folder.uri).fsPath;
|
|
143
|
+
const docs = await this.loadDlangFilesRecursively(folderPath, moduleDirectories, collector);
|
|
144
|
+
standaloneDocs.push(...docs);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Build all standalone documents in batch for performance
|
|
148
|
+
if (standaloneDocs.length > 0) {
|
|
149
|
+
await this.sharedServices.workspace.DocumentBuilder.build(standaloneDocs, {
|
|
150
|
+
validation: true
|
|
151
|
+
});
|
|
60
152
|
}
|
|
61
|
-
return undefined;
|
|
62
153
|
}
|
|
63
154
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
155
|
+
/**
|
|
156
|
+
* Recursively loads .dlang files from a directory.
|
|
157
|
+
* Skips module directories and the .dlang/cache package cache.
|
|
158
|
+
*/
|
|
159
|
+
private async loadDlangFilesRecursively(
|
|
160
|
+
dirPath: string,
|
|
161
|
+
moduleDirectories: Set<string>,
|
|
162
|
+
collector: (document: LangiumDocument) => void
|
|
163
|
+
): Promise<LangiumDocument[]> {
|
|
164
|
+
// Skip module directories - they're loaded via import graph
|
|
165
|
+
if (moduleDirectories.has(dirPath)) {
|
|
166
|
+
return [];
|
|
167
|
+
}
|
|
67
168
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
169
|
+
// Skip .dlang/cache - package cache managed by CLI
|
|
170
|
+
const baseName = path.basename(dirPath);
|
|
171
|
+
const parentName = path.basename(path.dirname(dirPath));
|
|
172
|
+
if (baseName === 'cache' && parentName === '.dlang') {
|
|
173
|
+
return [];
|
|
174
|
+
}
|
|
175
|
+
// Also skip the .dlang directory itself if it contains cache
|
|
176
|
+
if (baseName === '.dlang') {
|
|
177
|
+
return [];
|
|
178
|
+
}
|
|
73
179
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
180
|
+
const docs: LangiumDocument[] = [];
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
184
|
+
|
|
185
|
+
for (const entry of entries) {
|
|
186
|
+
const entryPath = path.join(dirPath, entry.name);
|
|
77
187
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
188
|
+
if (entry.isDirectory()) {
|
|
189
|
+
// Recurse into subdirectories
|
|
190
|
+
const subDocs = await this.loadDlangFilesRecursively(entryPath, moduleDirectories, collector);
|
|
191
|
+
docs.push(...subDocs);
|
|
192
|
+
} else if (this.isDlangFile(entry)) {
|
|
193
|
+
const doc = await this.tryLoadDocument(dirPath, entry.name, collector);
|
|
194
|
+
if (doc) {
|
|
195
|
+
docs.push(doc);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
81
198
|
}
|
|
82
|
-
|
|
199
|
+
} catch (error) {
|
|
200
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
201
|
+
console.warn(`Failed to read directory ${dirPath}: ${message}`);
|
|
83
202
|
}
|
|
203
|
+
|
|
204
|
+
return docs;
|
|
84
205
|
}
|
|
85
206
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
207
|
+
/**
|
|
208
|
+
* Checks if a directory entry is a .dlang file.
|
|
209
|
+
*/
|
|
210
|
+
private isDlangFile(entry: { isFile(): boolean; name: string }): boolean {
|
|
211
|
+
return entry.isFile() && entry.name.toLowerCase().endsWith('.dlang');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Attempts to load a document, returning undefined on failure.
|
|
216
|
+
*/
|
|
217
|
+
private async tryLoadDocument(
|
|
218
|
+
folderPath: string,
|
|
219
|
+
fileName: string,
|
|
220
|
+
collector: (document: LangiumDocument) => void
|
|
221
|
+
): Promise<LangiumDocument | undefined> {
|
|
222
|
+
const filePath = path.join(folderPath, fileName);
|
|
223
|
+
const uri = URI.file(filePath);
|
|
224
|
+
|
|
225
|
+
// Skip if already loaded (e.g., through imports)
|
|
226
|
+
if (this.langiumDocuments.hasDocument(uri)) {
|
|
92
227
|
return undefined;
|
|
93
228
|
}
|
|
94
|
-
}
|
|
95
229
|
|
|
96
|
-
private async pathExists(target: string): Promise<boolean> {
|
|
97
230
|
try {
|
|
98
|
-
await this.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
231
|
+
const doc = await this.langiumDocuments.getOrCreateDocument(uri);
|
|
232
|
+
collector(doc);
|
|
233
|
+
return doc;
|
|
234
|
+
} catch (error) {
|
|
235
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
236
|
+
console.warn(`Failed to load standalone file ${filePath}: ${message}`);
|
|
237
|
+
return undefined;
|
|
102
238
|
}
|
|
103
239
|
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Finds ALL model.yaml files in the workspace.
|
|
243
|
+
* Delegates to shared manifest utilities.
|
|
244
|
+
*
|
|
245
|
+
* @param folders - Workspace folders to search
|
|
246
|
+
* @returns Array of manifest info (one per model.yaml found)
|
|
247
|
+
*/
|
|
248
|
+
private async findAllManifestsInFolders(folders: WorkspaceFolder[]): Promise<Array<{ manifestPath: string; entryPath: string }>> {
|
|
249
|
+
const directories = folders.map(f => URI.parse(f.uri).fsPath);
|
|
250
|
+
return findManifestsInDirectories(directories);
|
|
251
|
+
}
|
|
104
252
|
}
|