@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
|
@@ -6,7 +6,7 @@ import type {
|
|
|
6
6
|
MaybePromise,
|
|
7
7
|
Reference
|
|
8
8
|
} from 'langium';
|
|
9
|
-
import { CstUtils,
|
|
9
|
+
import { CstUtils, isReference } from 'langium';
|
|
10
10
|
import type { LangiumServices } from 'langium/lsp';
|
|
11
11
|
import { AstNodeHoverProvider } from 'langium/lsp';
|
|
12
12
|
import type { Hover, HoverParams } from 'vscode-languageserver';
|
|
@@ -16,20 +16,54 @@ import { QualifiedNameProvider } from '../domain-lang-naming.js';
|
|
|
16
16
|
import { keywordExplanations } from './domain-lang-keywords.js';
|
|
17
17
|
import { effectiveClassification, effectiveTeam } from '../../sdk/resolution.js';
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Type-specific hover content generator.
|
|
21
|
+
* Returns undefined if the generator doesn't handle this node type.
|
|
22
|
+
*/
|
|
23
|
+
type HoverContentGenerator = (node: AstNode, commentBlock: string) => string | undefined;
|
|
24
|
+
|
|
19
25
|
/**
|
|
20
26
|
* Provides hover information for DomainLang elements.
|
|
27
|
+
*
|
|
28
|
+
* Extends Langium's AstNodeHoverProvider with DDD-specific hover content
|
|
29
|
+
* for domains, bounded contexts, relationships, and other DSL constructs.
|
|
21
30
|
*/
|
|
22
31
|
export class DomainLangHoverProvider extends AstNodeHoverProvider {
|
|
23
32
|
protected readonly documentationProvider: DocumentationProvider;
|
|
24
33
|
protected readonly commentProvider: CommentProvider;
|
|
25
34
|
protected readonly qualifiedNameProvider: QualifiedNameProvider;
|
|
26
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Registry of type-specific hover content generators.
|
|
38
|
+
* Each generator returns content for its node type, or undefined to skip.
|
|
39
|
+
*/
|
|
40
|
+
private readonly hoverGenerators: HoverContentGenerator[];
|
|
41
|
+
|
|
27
42
|
constructor(services: LangiumServices) {
|
|
28
43
|
super(services);
|
|
29
44
|
this.documentationProvider = services.documentation.DocumentationProvider;
|
|
30
45
|
this.commentProvider = services.documentation.CommentProvider;
|
|
31
46
|
const domainServices = services as DomainLangServices;
|
|
32
47
|
this.qualifiedNameProvider = domainServices.references.QualifiedNameProvider;
|
|
48
|
+
|
|
49
|
+
// Register type-specific generators
|
|
50
|
+
this.hoverGenerators = [
|
|
51
|
+
(node, comment) => this.getDomainHover(node, comment),
|
|
52
|
+
(node, comment) => this.getThisRefHover(node, comment),
|
|
53
|
+
(node, comment) => this.getBoundedContextHover(node, comment),
|
|
54
|
+
(node, comment) => this.getNamespaceHover(node, comment),
|
|
55
|
+
(node, comment) => this.getContextMapHover(node, comment),
|
|
56
|
+
(node, comment) => this.getDomainMapHover(node, comment),
|
|
57
|
+
(node, comment) => this.getDecisionHover(node, comment),
|
|
58
|
+
(node, comment) => this.getPolicyHover(node, comment),
|
|
59
|
+
(node, comment) => this.getBusinessRuleHover(node, comment),
|
|
60
|
+
(node, comment) => this.getDomainTermHover(node, comment),
|
|
61
|
+
(node, comment) => this.getTeamHover(node, comment),
|
|
62
|
+
(node, comment) => this.getClassificationHover(node, comment),
|
|
63
|
+
(node, comment) => this.getMetadataHover(node, comment),
|
|
64
|
+
(node, comment) => this.getRelationshipHover(node, comment),
|
|
65
|
+
(node, comment) => this.getImportHover(node, comment),
|
|
66
|
+
];
|
|
33
67
|
}
|
|
34
68
|
|
|
35
69
|
override async getHoverContent(document: LangiumDocument, params: HoverParams): Promise<Hover | undefined> {
|
|
@@ -45,35 +79,58 @@ export class DomainLangHoverProvider extends AstNodeHoverProvider {
|
|
|
45
79
|
return undefined;
|
|
46
80
|
}
|
|
47
81
|
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
if (
|
|
51
|
-
|
|
52
|
-
if (content) {
|
|
53
|
-
return { contents: { kind: 'markdown', value: content } };
|
|
54
|
-
}
|
|
82
|
+
// Try declaration hover first
|
|
83
|
+
const declarationHover = await this.tryGetDeclarationHover(cstNode);
|
|
84
|
+
if (declarationHover) {
|
|
85
|
+
return declarationHover;
|
|
55
86
|
}
|
|
56
87
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
88
|
+
// Then try keyword hover
|
|
89
|
+
return await this.tryGetKeywordHover(cstNode);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.error('Error in getHoverContent:', error);
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
63
95
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
96
|
+
/**
|
|
97
|
+
* Try to get hover for a declaration node (AST node).
|
|
98
|
+
*/
|
|
99
|
+
private async tryGetDeclarationHover(cstNode: ReturnType<typeof CstUtils.findDeclarationNodeAtOffset>): Promise<Hover | undefined> {
|
|
100
|
+
if (!cstNode) return undefined;
|
|
101
|
+
|
|
102
|
+
const targetNodes = this.references.findDeclarations(cstNode);
|
|
103
|
+
const targetNode = targetNodes?.[0];
|
|
104
|
+
if (targetNode) {
|
|
105
|
+
const content = await this.getAstNodeHoverContent(targetNode);
|
|
106
|
+
if (content) {
|
|
107
|
+
return { contents: { kind: 'markdown', value: content } };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
69
110
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
111
|
+
if (cstNode.astNode && ast.isThisRef(cstNode.astNode)) {
|
|
112
|
+
const content = await this.getAstNodeHoverContent(cstNode.astNode);
|
|
113
|
+
if (content) {
|
|
114
|
+
return { contents: { kind: 'markdown', value: content } };
|
|
74
115
|
}
|
|
75
|
-
}
|
|
76
|
-
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Try to get hover for a keyword node.
|
|
123
|
+
* Uses the keyword dictionary for all keywords.
|
|
124
|
+
*/
|
|
125
|
+
private async tryGetKeywordHover(cstNode: ReturnType<typeof CstUtils.findDeclarationNodeAtOffset>): Promise<Hover | undefined> {
|
|
126
|
+
if (!cstNode || cstNode.grammarSource?.$type !== 'Keyword') {
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Use the keyword dictionary for hover content
|
|
131
|
+
const explanation = keywordExplanations[cstNode.text.toLowerCase()];
|
|
132
|
+
if (explanation) {
|
|
133
|
+
return { contents: { kind: 'markdown', value: `💡 ${explanation}` } };
|
|
77
134
|
}
|
|
78
135
|
|
|
79
136
|
return undefined;
|
|
@@ -84,211 +141,276 @@ export class DomainLangHoverProvider extends AstNodeHoverProvider {
|
|
|
84
141
|
const content = this.documentationProvider.getDocumentation(node);
|
|
85
142
|
const commentBlock = content ? `*${content}*\n\n` : '';
|
|
86
143
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const signatureParts = ['Domain', node.name];
|
|
94
|
-
if (node.parent?.ref?.name) signatureParts.push('in', node.parent.ref.name);
|
|
95
|
-
const signature = `\`\`\`domain-lang\n${signatureParts.join(' ')}\n\`\`\``;
|
|
96
|
-
|
|
97
|
-
const fields: string[] = [signature];
|
|
98
|
-
if (description) fields.push(description);
|
|
99
|
-
if (vision || type || node.parent) fields.push('---');
|
|
100
|
-
if (vision) fields.push(`**Vision:** ${vision}`);
|
|
101
|
-
if (type) fields.push(`**Type:** ${this.refLink(typeRef, type)}`);
|
|
102
|
-
if (node.parent) fields.push(`**Parent:** ${this.refLink(node.parent)}`);
|
|
103
|
-
|
|
104
|
-
return (commentBlock ? `${commentBlock}\n\n---\n\n` : '') +
|
|
105
|
-
`📁 **\`(domain)\` ${node.name}**\n\n` +
|
|
106
|
-
fields.join('\n\n');
|
|
144
|
+
// Try each type-specific generator
|
|
145
|
+
for (const generator of this.hoverGenerators) {
|
|
146
|
+
const result = generator(node, commentBlock);
|
|
147
|
+
if (result !== undefined) {
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
107
150
|
}
|
|
108
151
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
ast.isContextMap(parent) ||
|
|
117
|
-
ast.isDomainMap(parent) ||
|
|
118
|
-
ast.isModel(parent)
|
|
119
|
-
) {
|
|
120
|
-
return this.getAstNodeHoverContent(parent);
|
|
121
|
-
}
|
|
122
|
-
parent = parent.$container;
|
|
123
|
-
}
|
|
152
|
+
// Default fallback for unknown types
|
|
153
|
+
return this.getDefaultHover(node, commentBlock);
|
|
154
|
+
} catch (error) {
|
|
155
|
+
console.error('Error in getAstNodeHoverContent:', error);
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
124
159
|
|
|
125
|
-
|
|
126
|
-
|
|
160
|
+
// ============================================================
|
|
161
|
+
// Type-specific hover generators
|
|
162
|
+
// ============================================================
|
|
127
163
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const classification = effectiveClassification(node);
|
|
131
|
-
const team = effectiveTeam(node);
|
|
132
|
-
const businessModel = node.businessModel?.ref;
|
|
133
|
-
const evolution = node.evolution?.ref;
|
|
134
|
-
const relationships = node.relationships ?? [];
|
|
135
|
-
const terminology = node.terminology ?? [];
|
|
136
|
-
const decisions = node.decisions ?? [];
|
|
137
|
-
const classificationName = classification?.name;
|
|
138
|
-
const teamName = team?.name;
|
|
139
|
-
|
|
140
|
-
const signatureParts = ['boundedcontext', node.name];
|
|
141
|
-
if (node.domain?.ref?.name) signatureParts.push('for', node.domain.ref.name);
|
|
142
|
-
if (classificationName) signatureParts.push('as', classificationName);
|
|
143
|
-
if (teamName) signatureParts.push('by', teamName);
|
|
144
|
-
const signature = `\`\`\`domain-lang\n${signatureParts.join(' ')}\n\`\`\``;
|
|
145
|
-
|
|
146
|
-
const fields: string[] = [signature];
|
|
147
|
-
if (description) fields.push(description);
|
|
148
|
-
if (classification || team || businessModel || evolution) fields.push('---');
|
|
149
|
-
if (classification) fields.push(`🔖 **Classification:** ${this.refLink(classification)}`);
|
|
150
|
-
if (team) fields.push(`👥 **Team:** ${this.refLink(team)}`);
|
|
151
|
-
if (businessModel) fields.push(`💼 **Business Model:** ${this.refLink(businessModel)}`);
|
|
152
|
-
if (evolution) fields.push(`🔄 **Evolution:** ${this.refLink(evolution)}`);
|
|
153
|
-
if (relationships.length) {
|
|
154
|
-
const relationshipLines = relationships.map(rel => `- ${this.refLink(rel.left?.link)} ${rel.arrow} ${this.refLink(rel.right?.link)}${rel.type ? ` \`${rel.type}\`` : ''}`);
|
|
155
|
-
fields.push(`**Relationships:**\n${relationshipLines.join('\n')}`);
|
|
156
|
-
}
|
|
157
|
-
if (terminology.length) {
|
|
158
|
-
const termLines = terminology.map(t => `- \`${t.name}\`: ${t.meaning ?? ''}`);
|
|
159
|
-
fields.push(`**Terminology:**\n${termLines.join('\n')}`);
|
|
160
|
-
}
|
|
161
|
-
if (decisions.length) {
|
|
162
|
-
const decisionLines = decisions.map(d => `- \`${d.name}\`: ${d.value ?? ''}`);
|
|
163
|
-
fields.push(`**Decisions:**\n${decisionLines.join('\n')}`);
|
|
164
|
-
}
|
|
164
|
+
private getDomainHover(node: AstNode, commentBlock: string): string | undefined {
|
|
165
|
+
if (!ast.isDomain(node)) return undefined;
|
|
165
166
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
167
|
+
const description = node.description ?? '';
|
|
168
|
+
const vision = node.vision ?? '';
|
|
169
|
+
const typeRef = node.type?.ref;
|
|
170
|
+
const type = this.getRefName(typeRef);
|
|
170
171
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
`🧭 **\`(namespace)\` ${node.name}**\n\n` +
|
|
175
|
-
fields.join('\n\n');
|
|
176
|
-
}
|
|
172
|
+
const signatureParts = ['Domain', node.name];
|
|
173
|
+
if (node.parent?.ref?.name) signatureParts.push('in', node.parent.ref.name);
|
|
174
|
+
const signature = this.codeBlock(signatureParts.join(' '));
|
|
177
175
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
if (node.relationships.length) {
|
|
185
|
-
fields.push('---');
|
|
186
|
-
fields.push(`**🔗 Relationships**\n${node.relationships.map(r => `- ${this.refLink(r.left?.link)} ${r.arrow} ${this.refLink(r.right?.link)}${r.type ? ` \`${r.type}\`` : ''}`).join('\n')}`);
|
|
187
|
-
}
|
|
188
|
-
return (commentBlock ? `${commentBlock}\n\n---\n\n` : '') +
|
|
189
|
-
`🗺️ **\`(contextmap)\` ${node.name}**\n\n` +
|
|
190
|
-
fields.join('\n\n');
|
|
191
|
-
}
|
|
176
|
+
const fields: string[] = [signature];
|
|
177
|
+
if (description) fields.push(description);
|
|
178
|
+
if (vision || type || node.parent) fields.push('---');
|
|
179
|
+
if (vision) fields.push(`**Vision:** ${vision}`);
|
|
180
|
+
if (type) fields.push(`**Type:** ${this.refLink(typeRef, type)}`);
|
|
181
|
+
if (node.parent) fields.push(`**Parent:** ${this.refLink(node.parent)}`);
|
|
192
182
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
if (node.domains.length) {
|
|
196
|
-
fields.push('---');
|
|
197
|
-
fields.push(`**📁 Domains**\n${node.domains.flatMap(d => d.items.map(item => `- ${this.refLink(item.ref)}`)).join('\n')}`);
|
|
198
|
-
}
|
|
199
|
-
return (commentBlock ? `${commentBlock}\n\n---\n\n` : '') +
|
|
200
|
-
`🗺️ **\`(domainmap)\` ${node.name}**\n\n` +
|
|
201
|
-
fields.join('\n\n');
|
|
202
|
-
}
|
|
183
|
+
return this.formatHover(commentBlock, '📁', 'domain', node.name, fields);
|
|
184
|
+
}
|
|
203
185
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
186
|
+
private getThisRefHover(node: AstNode, _commentBlock: string): string | undefined {
|
|
187
|
+
if (!ast.isThisRef(node)) return undefined;
|
|
188
|
+
|
|
189
|
+
let parent = node.$container;
|
|
190
|
+
while (parent) {
|
|
191
|
+
if (
|
|
192
|
+
ast.isDomain(parent) ||
|
|
193
|
+
ast.isBoundedContext(parent) ||
|
|
194
|
+
ast.isNamespaceDeclaration(parent) ||
|
|
195
|
+
ast.isContextMap(parent) ||
|
|
196
|
+
ast.isDomainMap(parent) ||
|
|
197
|
+
ast.isModel(parent)
|
|
198
|
+
) {
|
|
199
|
+
return this.getAstNodeHoverContent(parent) as string | undefined;
|
|
213
200
|
}
|
|
201
|
+
parent = parent.$container;
|
|
202
|
+
}
|
|
214
203
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
if (node.value) {
|
|
218
|
-
fields.push('---');
|
|
219
|
-
fields.push(`*Definition:* ${node.value}`);
|
|
220
|
-
}
|
|
221
|
-
return (commentBlock ? `${commentBlock}\n\n---\n\n` : '') +
|
|
222
|
-
`📜 **\`(policy)\` ${node.name}**\n\n` +
|
|
223
|
-
fields.join('\n\n');
|
|
224
|
-
}
|
|
204
|
+
return '*this* refers to the current context';
|
|
205
|
+
}
|
|
225
206
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
207
|
+
private getBoundedContextHover(node: AstNode, commentBlock: string): string | undefined {
|
|
208
|
+
if (!ast.isBoundedContext(node)) return undefined;
|
|
209
|
+
|
|
210
|
+
const description = node.description ?? '';
|
|
211
|
+
const classification = effectiveClassification(node);
|
|
212
|
+
const team = effectiveTeam(node);
|
|
213
|
+
const businessModel = node.businessModel?.ref;
|
|
214
|
+
const evolution = node.evolution?.ref;
|
|
215
|
+
const relationships = node.relationships ?? [];
|
|
216
|
+
const terminology = node.terminology ?? [];
|
|
217
|
+
const decisions = node.decisions ?? [];
|
|
218
|
+
const classificationName = classification?.name;
|
|
219
|
+
const teamName = team?.name;
|
|
220
|
+
|
|
221
|
+
const signatureParts = ['boundedcontext', node.name];
|
|
222
|
+
if (node.domain?.ref?.name) signatureParts.push('for', node.domain.ref.name);
|
|
223
|
+
if (classificationName) signatureParts.push('as', classificationName);
|
|
224
|
+
if (teamName) signatureParts.push('by', teamName);
|
|
225
|
+
const signature = this.codeBlock(signatureParts.join(' '));
|
|
226
|
+
|
|
227
|
+
const fields: string[] = [signature];
|
|
228
|
+
if (description) fields.push(description);
|
|
229
|
+
if (classification || team || businessModel || evolution) fields.push('---');
|
|
230
|
+
if (classification) fields.push(`🔖 **Classification:** ${this.refLink(classification)}`);
|
|
231
|
+
if (team) fields.push(`👥 **Team:** ${this.refLink(team)}`);
|
|
232
|
+
if (businessModel) fields.push(`💼 **Business Model:** ${this.refLink(businessModel)}`);
|
|
233
|
+
if (evolution) fields.push(`🔄 **Evolution:** ${this.refLink(evolution)}`);
|
|
234
|
+
|
|
235
|
+
if (relationships.length > 0) {
|
|
236
|
+
const lines = relationships.map(rel => this.formatRelationshipLine(rel));
|
|
237
|
+
fields.push(`**Relationships:**\n${lines.join('\n')}`);
|
|
238
|
+
}
|
|
239
|
+
if (terminology.length > 0) {
|
|
240
|
+
const lines = terminology.map(t => `- \`${t.name}\`: ${t.meaning ?? ''}`);
|
|
241
|
+
fields.push(`**Terminology:**\n${lines.join('\n')}`);
|
|
242
|
+
}
|
|
243
|
+
if (decisions.length > 0) {
|
|
244
|
+
const lines = decisions.map(d => `- \`${d.name}\`: ${d.value ?? ''}`);
|
|
245
|
+
fields.push(`**Decisions:**\n${lines.join('\n')}`);
|
|
246
|
+
}
|
|
236
247
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
if (node.meaning) {
|
|
240
|
-
fields.push('---');
|
|
241
|
-
fields.push(`*${node.meaning}*`);
|
|
242
|
-
}
|
|
243
|
-
return (commentBlock ? `${commentBlock}\n\n---\n\n` : '') +
|
|
244
|
-
`🗝️ **\`(term)\` ${node.name}**\n\n` +
|
|
245
|
-
fields.join('\n\n');
|
|
246
|
-
}
|
|
248
|
+
return this.formatHover(commentBlock, '📕', 'boundedcontext', node.name, fields);
|
|
249
|
+
}
|
|
247
250
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
`👥 **\`(team)\` ${node.name}**`;
|
|
251
|
-
}
|
|
251
|
+
private getNamespaceHover(node: AstNode, commentBlock: string): string | undefined {
|
|
252
|
+
if (!ast.isNamespaceDeclaration(node)) return undefined;
|
|
252
253
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}
|
|
254
|
+
const fields: string[] = [`Contains ${node.children.length} elements.`];
|
|
255
|
+
return this.formatHover(commentBlock, '🧭', 'namespace', node.name, fields);
|
|
256
|
+
}
|
|
257
257
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
`🔖 **\`(metadata)\` ${node.name}**`;
|
|
261
|
-
}
|
|
258
|
+
private getContextMapHover(node: AstNode, commentBlock: string): string | undefined {
|
|
259
|
+
if (!ast.isContextMap(node)) return undefined;
|
|
262
260
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
}
|
|
261
|
+
const fields: string[] = [];
|
|
262
|
+
if (node.boundedContexts.length > 0) {
|
|
263
|
+
fields.push('---');
|
|
264
|
+
const items = node.boundedContexts.flatMap(bc =>
|
|
265
|
+
bc.items.map(item => `- ${this.refLink(item.ref)}`)
|
|
266
|
+
);
|
|
267
|
+
fields.push(`**📕 Bounded Contexts**\n${items.join('\n')}`);
|
|
268
|
+
}
|
|
269
|
+
if (node.relationships.length > 0) {
|
|
270
|
+
fields.push('---');
|
|
271
|
+
const lines = node.relationships.map(r => this.formatRelationshipLine(r));
|
|
272
|
+
fields.push(`**🔗 Relationships**\n${lines.join('\n')}`);
|
|
273
|
+
}
|
|
274
|
+
return this.formatHover(commentBlock, '🗺️', 'contextmap', node.name, fields);
|
|
275
|
+
}
|
|
275
276
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
fields.push(`**URI:** \`${node.uri}\``);
|
|
279
|
-
if (node.alias) fields.push(`**Alias:** \`${node.alias}\``);
|
|
280
|
-
return (commentBlock ? `${commentBlock}\n\n---\n\n` : '') +
|
|
281
|
-
`📦 **\`(import)\`**\n\n` +
|
|
282
|
-
fields.join('\n\n');
|
|
283
|
-
}
|
|
277
|
+
private getDomainMapHover(node: AstNode, commentBlock: string): string | undefined {
|
|
278
|
+
if (!ast.isDomainMap(node)) return undefined;
|
|
284
279
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
280
|
+
const fields: string[] = [];
|
|
281
|
+
if (node.domains.length > 0) {
|
|
282
|
+
fields.push('---');
|
|
283
|
+
const items = node.domains.flatMap(d =>
|
|
284
|
+
d.items.map(item => `- ${this.refLink(item.ref)}`)
|
|
285
|
+
);
|
|
286
|
+
fields.push(`**📁 Domains**\n${items.join('\n')}`);
|
|
287
|
+
}
|
|
288
|
+
return this.formatHover(commentBlock, '🗺️', 'domainmap', node.name, fields);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
private getDecisionHover(node: AstNode, commentBlock: string): string | undefined {
|
|
292
|
+
if (!ast.isDecision(node)) return undefined;
|
|
293
|
+
|
|
294
|
+
const fields: string[] = [];
|
|
295
|
+
if (node.value) {
|
|
296
|
+
fields.push('---', `*Definition:* ${node.value}`);
|
|
297
|
+
}
|
|
298
|
+
return this.formatHover(commentBlock, '⚖️', 'decision', node.name, fields);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
private getPolicyHover(node: AstNode, commentBlock: string): string | undefined {
|
|
302
|
+
if (!ast.isPolicy(node)) return undefined;
|
|
303
|
+
|
|
304
|
+
const fields: string[] = [];
|
|
305
|
+
if (node.value) {
|
|
306
|
+
fields.push('---', `*Definition:* ${node.value}`);
|
|
307
|
+
}
|
|
308
|
+
return this.formatHover(commentBlock, '📜', 'policy', node.name, fields);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
private getBusinessRuleHover(node: AstNode, commentBlock: string): string | undefined {
|
|
312
|
+
if (!ast.isBusinessRule(node)) return undefined;
|
|
313
|
+
|
|
314
|
+
const fields: string[] = [];
|
|
315
|
+
if (node.value) {
|
|
316
|
+
fields.push('---', `*Definition:* ${node.value}`);
|
|
291
317
|
}
|
|
318
|
+
return this.formatHover(commentBlock, '⚖️', 'rule', node.name, fields);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
private getDomainTermHover(node: AstNode, commentBlock: string): string | undefined {
|
|
322
|
+
if (!ast.isDomainTerm(node)) return undefined;
|
|
323
|
+
|
|
324
|
+
const fields: string[] = [];
|
|
325
|
+
if (node.meaning) {
|
|
326
|
+
fields.push('---', `*${node.meaning}*`);
|
|
327
|
+
}
|
|
328
|
+
return this.formatHover(commentBlock, '🗝️', 'term', node.name, fields);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
private getTeamHover(node: AstNode, commentBlock: string): string | undefined {
|
|
332
|
+
if (!ast.isTeam(node)) return undefined;
|
|
333
|
+
return this.formatHover(commentBlock, '👥', 'team', node.name, []);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
private getClassificationHover(node: AstNode, commentBlock: string): string | undefined {
|
|
337
|
+
if (!ast.isClassification(node)) return undefined;
|
|
338
|
+
return this.formatHover(commentBlock, '🏷️', 'classification', node.name, []);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
private getMetadataHover(node: AstNode, commentBlock: string): string | undefined {
|
|
342
|
+
if (!ast.isMetadata(node)) return undefined;
|
|
343
|
+
return this.formatHover(commentBlock, '🔖', 'metadata', node.name, []);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
private getRelationshipHover(node: AstNode, commentBlock: string): string | undefined {
|
|
347
|
+
if (!ast.isRelationship(node)) return undefined;
|
|
348
|
+
|
|
349
|
+
const leftPatterns = node.leftPatterns.join(', ');
|
|
350
|
+
const rightPatterns = node.rightPatterns.join(', ');
|
|
351
|
+
const fields: string[] = [
|
|
352
|
+
`${this.refLink(node.left.link)} ${node.arrow} ${this.refLink(node.right.link)}`
|
|
353
|
+
];
|
|
354
|
+
if (node.type) fields.push(`**Type:** \`${node.type}\``);
|
|
355
|
+
if (leftPatterns) fields.push(`**Left patterns:** ${leftPatterns}`);
|
|
356
|
+
if (rightPatterns) fields.push(`**Right patterns:** ${rightPatterns}`);
|
|
357
|
+
|
|
358
|
+
return this.formatHover(commentBlock, '🔗', 'relationship', undefined, fields);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
private getImportHover(node: AstNode, commentBlock: string): string | undefined {
|
|
362
|
+
if (!ast.isImportStatement(node)) return undefined;
|
|
363
|
+
|
|
364
|
+
const fields: string[] = [`**URI:** \`${node.uri}\``];
|
|
365
|
+
if (node.alias) fields.push(`**Alias:** \`${node.alias}\``);
|
|
366
|
+
return this.formatHover(commentBlock, '📦', 'import', undefined, fields);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
private getDefaultHover(node: AstNode, commentBlock: string): string {
|
|
370
|
+
const title = ast.isType(node) ? node.name : node.$type.toLowerCase();
|
|
371
|
+
const typeName = node.$type.toLowerCase();
|
|
372
|
+
const name = ast.isType(node) ? ` ${title}` : '';
|
|
373
|
+
|
|
374
|
+
const separator = commentBlock ? `${commentBlock}\n\n---\n\n` : '';
|
|
375
|
+
return `${separator}ℹ️ **\`(${typeName})\`${name}**`;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// ============================================================
|
|
379
|
+
// Helper methods
|
|
380
|
+
// ============================================================
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Formats a relationship line for hover display.
|
|
384
|
+
*/
|
|
385
|
+
private formatRelationshipLine(rel: ast.Relationship): string {
|
|
386
|
+
const left = this.refLink(rel.left?.link);
|
|
387
|
+
const right = this.refLink(rel.right?.link);
|
|
388
|
+
const type = rel.type ? ` \`${rel.type}\`` : '';
|
|
389
|
+
return `- ${left} ${rel.arrow} ${right}${type}`;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Wraps text in a domain-lang code block.
|
|
394
|
+
*/
|
|
395
|
+
private codeBlock(text: string): string {
|
|
396
|
+
return `\`\`\`domain-lang\n${text}\n\`\`\``;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Formats the final hover content with consistent structure.
|
|
401
|
+
*/
|
|
402
|
+
private formatHover(
|
|
403
|
+
commentBlock: string,
|
|
404
|
+
emoji: string,
|
|
405
|
+
typeName: string,
|
|
406
|
+
name: string | undefined,
|
|
407
|
+
fields: string[]
|
|
408
|
+
): string {
|
|
409
|
+
const separator = commentBlock ? `${commentBlock}\n\n---\n\n` : '';
|
|
410
|
+
const nameDisplay = name ? ` ${name}` : '';
|
|
411
|
+
const header = `${emoji} **\`(${typeName})\`${nameDisplay}**`;
|
|
412
|
+
const body = fields.length > 0 ? `\n\n${fields.join('\n\n')}` : '';
|
|
413
|
+
return `${separator}${header}${body}`;
|
|
292
414
|
}
|
|
293
415
|
|
|
294
416
|
private getRefName(ref: ast.Type | Reference<ast.Type> | undefined): string {
|
|
@@ -318,21 +440,5 @@ export class DomainLangHoverProvider extends AstNodeHoverProvider {
|
|
|
318
440
|
return '';
|
|
319
441
|
}
|
|
320
442
|
|
|
321
|
-
protected override getKeywordHoverContent(node: AstNode): MaybePromise<Hover | undefined> {
|
|
322
|
-
let comment = isAstNodeWithComment(node) ? node.$comment : undefined;
|
|
323
|
-
|
|
324
|
-
if (!comment) {
|
|
325
|
-
comment = CstUtils.findCommentNode(node.$cstNode, ['ML_COMMENT'])?.text;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
if (comment && isJSDoc(comment)) {
|
|
329
|
-
const markdown = parseJSDoc(comment).toMarkdown();
|
|
330
|
-
if (markdown) {
|
|
331
|
-
return { contents: { kind: 'markdown', value: markdown } };
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
return undefined;
|
|
336
|
-
}
|
|
337
443
|
}
|
|
338
444
|
|