@likec4/language-server 1.5.0 → 1.6.1
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/contrib/likec4.tmLanguage.json +1 -1
- package/package.json +17 -5
- package/src/Rpc.ts +2 -1
- package/src/ast.ts +10 -5
- package/src/generated/ast.ts +355 -238
- package/src/generated/grammar.ts +1 -1
- package/src/generated-lib/icons.ts +952 -0
- package/src/like-c4.langium +85 -54
- package/src/likec4lib.ts +7 -0
- package/src/lsp/DocumentSymbolProvider.ts +28 -1
- package/src/lsp/SemanticTokenProvider.ts +30 -9
- package/src/model/model-parser.ts +126 -74
- package/src/model-change/changeElementStyle.ts +4 -4
- package/src/model-graph/compute-view/__test__/fixture.ts +3 -3
- package/src/model-graph/compute-view/compute.ts +4 -4
- package/src/model-graph/dynamic-view/__test__/fixture.ts +2 -2
- package/src/references/scope-computation.ts +113 -60
- package/src/shared/NodeKindProvider.ts +1 -0
- package/src/shared/WorkspaceManager.ts +15 -6
- package/src/validation/dynamic-view-rule.ts +19 -24
- package/src/validation/index.ts +8 -7
- package/src/validation/property-checks.ts +23 -1
- package/src/validation/view-predicates/custom-element-expr.ts +9 -7
- package/src/validation/view-predicates/custom-relation-expr.ts +4 -3
- package/src/validation/view-predicates/expanded-element.ts +13 -24
- package/src/validation/view-predicates/incoming.ts +5 -5
- package/src/validation/view-predicates/outgoing.ts +5 -5
package/src/like-c4.langium
CHANGED
|
@@ -4,10 +4,22 @@ entry LikeC4Grammar:
|
|
|
4
4
|
(
|
|
5
5
|
specifications+=SpecificationRule |
|
|
6
6
|
models+=Model |
|
|
7
|
-
views+=ModelViews
|
|
7
|
+
views+=ModelViews |
|
|
8
|
+
likec4lib+=LikeC4Lib
|
|
8
9
|
)*
|
|
9
10
|
;
|
|
10
11
|
|
|
12
|
+
// Lib -------------------------------------
|
|
13
|
+
LikeC4Lib:
|
|
14
|
+
'likec4lib' '{'
|
|
15
|
+
'icons' '{'
|
|
16
|
+
(icons+=LibIcon)+
|
|
17
|
+
'}'
|
|
18
|
+
'}';
|
|
19
|
+
|
|
20
|
+
LibIcon:
|
|
21
|
+
name=IconId;
|
|
22
|
+
|
|
11
23
|
// Specification -------------------------------------
|
|
12
24
|
|
|
13
25
|
ElementKind:
|
|
@@ -80,7 +92,7 @@ ElementBody: '{'
|
|
|
80
92
|
;
|
|
81
93
|
|
|
82
94
|
ElementProperty:
|
|
83
|
-
ElementStringProperty | StyleProperties | LinkProperty;
|
|
95
|
+
ElementStringProperty | StyleProperties | LinkProperty | IconProperty;
|
|
84
96
|
|
|
85
97
|
ElementStringProperty:
|
|
86
98
|
key=('title' | 'technology' | 'description') ':'? value=String ';'?;
|
|
@@ -214,84 +226,95 @@ DynamicViewRule:
|
|
|
214
226
|
;
|
|
215
227
|
|
|
216
228
|
DynamicViewStep:
|
|
217
|
-
source=ElementRef (isBackward?='<-' | '->' | '-[' kind=[RelationshipKind] ']->') target=ElementRef title=String?
|
|
229
|
+
source=ElementRef (isBackward?='<-' | '->' | '-[' kind=[RelationshipKind] ']->' | kind=[RelationshipKind:DotId] ) target=ElementRef title=String?
|
|
218
230
|
;
|
|
219
231
|
|
|
220
232
|
ViewRulePredicate:
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
;
|
|
224
|
-
|
|
225
|
-
DynamicViewRulePredicate:
|
|
226
|
-
'include' expressions+=ElementPredicateExpression (',' expressions+=ElementPredicateExpression)* ','?
|
|
233
|
+
{infer IncludePredicate} 'include' exprs=Expressions |
|
|
234
|
+
{infer ExcludePredicate} 'exclude' exprs=Expressions
|
|
227
235
|
;
|
|
228
236
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
237
|
+
// IncludePredicate:
|
|
238
|
+
// 'include' expr=WithExpressions ({infer IncludePredicate.prev=current} ',' (expr=WithExpressions)?)*
|
|
239
|
+
// ;
|
|
240
|
+
// ExcludePredicate:
|
|
241
|
+
// 'exclude' expr=WithExpressions ({infer ExcludePredicate.prev=current} ',' (expr=WithExpressions)?)*
|
|
242
|
+
// ;
|
|
233
243
|
|
|
234
|
-
|
|
235
|
-
'
|
|
244
|
+
Expressions:
|
|
245
|
+
value=WithExpressions ({infer Expressions.prev=current} ',' (value=WithExpressions)?)*
|
|
246
|
+
;
|
|
236
247
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
248
|
+
WithExpressions infers Expression:
|
|
249
|
+
RelationExpression ({infer CustomRelationExpression.relation=current} 'with' custom=CustomRelationProperties)? |
|
|
250
|
+
ElementExpression ({infer CustomElementExpression.target=current} 'with' custom=CustomElementProperties)?
|
|
240
251
|
;
|
|
241
252
|
|
|
242
|
-
|
|
243
|
-
|
|
253
|
+
ElementExpression:
|
|
254
|
+
ElementSelectorExpression |
|
|
255
|
+
ElementDescedantsExpression
|
|
244
256
|
;
|
|
245
257
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
258
|
+
ElementSelectorExpression infers ElementExpression:
|
|
259
|
+
{infer WildcardExpression} isWildcard?='*' |
|
|
260
|
+
{infer ElementTagExpression} 'element.tag' IsEqual tag=[Tag:TagId]? |
|
|
261
|
+
{infer ElementKindExpression} 'element.kind' IsEqual kind=[ElementKind]?
|
|
250
262
|
;
|
|
251
263
|
|
|
252
|
-
|
|
253
|
-
|
|
264
|
+
ElementDescedantsExpression infers ElementExpression:
|
|
265
|
+
ElementRef (
|
|
266
|
+
{infer ExpandElementExpression.expand=current} DotUnderscore |
|
|
267
|
+
{infer ElementDescedantsExpression.parent=current} DotWildcard
|
|
268
|
+
)?
|
|
254
269
|
;
|
|
255
270
|
|
|
256
|
-
|
|
257
|
-
|
|
271
|
+
RelationExpression:
|
|
272
|
+
InOutRelationExpressions |
|
|
273
|
+
DirectedRelationExpressions
|
|
258
274
|
;
|
|
259
275
|
|
|
260
|
-
|
|
261
|
-
|
|
276
|
+
InOutRelationExpressions infers RelationExpression:
|
|
277
|
+
IncomingRelationExpression ({infer InOutRelationExpression.inout=current} '->')?
|
|
262
278
|
;
|
|
263
279
|
|
|
264
|
-
|
|
265
|
-
|
|
280
|
+
IncomingRelationExpression:
|
|
281
|
+
'->' to=ElementExpression;
|
|
266
282
|
|
|
267
|
-
|
|
268
|
-
|
|
283
|
+
DirectedRelationExpressions infers RelationExpression:
|
|
284
|
+
OutgoingRelationExpression ({infer DirectedRelationExpression.source=current} target=ElementExpression)?
|
|
285
|
+
;
|
|
269
286
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
DescedantsExpr
|
|
287
|
+
OutgoingRelationExpression:
|
|
288
|
+
from=ElementExpression
|
|
289
|
+
(isBidirectional?='<->' | '->' | '-[' kind=[RelationshipKind] ']->' | kind=[RelationshipKind:DotId])
|
|
274
290
|
;
|
|
275
291
|
|
|
276
|
-
|
|
277
|
-
|
|
292
|
+
// Comma-separated list of ElementExpressions
|
|
293
|
+
ElementExpressionsIterator:
|
|
294
|
+
value=ElementExpression ({infer ElementExpressionsIterator.prev=current} ',' (value=ElementExpression)?)*
|
|
278
295
|
;
|
|
279
296
|
|
|
280
|
-
|
|
281
|
-
'
|
|
282
|
-
{infer ElementTagExpr} 'tag' IsEqual tag=[Tag:TagId] |
|
|
283
|
-
{infer ElementKindExpr} 'kind' IsEqual kind=[ElementKind]
|
|
284
|
-
)
|
|
297
|
+
DynamicViewRulePredicate:
|
|
298
|
+
'include' exprs=DynamicViewRulePredicateIterator
|
|
285
299
|
;
|
|
286
300
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
301
|
+
DynamicViewRulePredicateIterator:
|
|
302
|
+
value=DynamicViewElementExpressions ({infer DynamicViewRulePredicateIterator.prev=current} ',' (value=DynamicViewElementExpressions)?)*
|
|
303
|
+
;
|
|
304
|
+
|
|
305
|
+
DynamicViewElementExpressions infers DynamicViewElementExpression:
|
|
306
|
+
ElementExpression ({infer CustomElementExpression.target=current} 'with' custom=CustomElementProperties)?
|
|
292
307
|
;
|
|
293
308
|
|
|
294
|
-
|
|
309
|
+
ViewRuleStyle:
|
|
310
|
+
'style' target=ElementExpressionsIterator '{'
|
|
311
|
+
props+=StyleProperty*
|
|
312
|
+
'}';
|
|
313
|
+
|
|
314
|
+
ViewRuleAutoLayout:
|
|
315
|
+
'autoLayout' direction=ViewLayoutDirection;
|
|
316
|
+
|
|
317
|
+
CustomElementProperties: '{'
|
|
295
318
|
props+=(
|
|
296
319
|
NavigateToProperty |
|
|
297
320
|
ElementStringProperty |
|
|
@@ -300,7 +323,7 @@ CustomElementExprBody: '{'
|
|
|
300
323
|
'}'
|
|
301
324
|
;
|
|
302
325
|
|
|
303
|
-
|
|
326
|
+
CustomRelationProperties: '{'
|
|
304
327
|
props+=(
|
|
305
328
|
RelationStringProperty |
|
|
306
329
|
RelationshipStyleProperty
|
|
@@ -323,7 +346,7 @@ OpacityProperty:
|
|
|
323
346
|
|
|
324
347
|
// Element properties -------------------------------------
|
|
325
348
|
IconProperty:
|
|
326
|
-
key='icon' ':'? value=Uri ';'?;
|
|
349
|
+
key='icon' ':'? (libicon=[LibIcon:IconId] | value=Uri) ';'?;
|
|
327
350
|
|
|
328
351
|
ShapeProperty:
|
|
329
352
|
key='shape' ':'? value=ElementShape ';'?;
|
|
@@ -381,6 +404,11 @@ ThemeColor returns string:
|
|
|
381
404
|
'primary' | 'secondary' | 'muted' | 'slate' | 'blue' | 'indigo' | 'sky' | 'red' | 'gray' | 'green' | 'amber';
|
|
382
405
|
ElementShape returns string:
|
|
383
406
|
'rectangle' | 'person' | 'browser' | 'mobile' | 'cylinder' | 'storage' | 'queue';
|
|
407
|
+
|
|
408
|
+
IconId returns string:
|
|
409
|
+
LIB_ICON;
|
|
410
|
+
// ('aws:' | 'gcp:' | 'tech:') IdTerminal;
|
|
411
|
+
|
|
384
412
|
Uri returns string:
|
|
385
413
|
URI_WITH_SCHEMA | URI_RELATIVE;
|
|
386
414
|
|
|
@@ -418,6 +446,9 @@ hidden terminal WS: /\s+/;
|
|
|
418
446
|
// Terminals
|
|
419
447
|
//terminal LineStartWithDash: /(?<=([\r?\n][^\S\r\n]*))-/;
|
|
420
448
|
|
|
449
|
+
// LibIcons
|
|
450
|
+
terminal LIB_ICON: /\b(aws|gcp|tech):[-\w]*/;
|
|
451
|
+
|
|
421
452
|
terminal URI_WITH_SCHEMA: /\w+:\/\/\S+/;
|
|
422
453
|
terminal URI_RELATIVE: /\.{0,2}\/[^\/]\S+/;
|
|
423
454
|
|
|
@@ -436,4 +467,4 @@ terminal String: /"[^"]*"|'[^']*'/;
|
|
|
436
467
|
|
|
437
468
|
// terminal TagId: HASH LETTER (LETTER | DIGIT | UNDERSCORE | DASH)*;
|
|
438
469
|
// terminal IdTerminal: (LETTER | UNDERSCORE+ (LETTER | DIGIT)) (LETTER | DIGIT | UNDERSCORE | DASH)*;
|
|
439
|
-
terminal IdTerminal:
|
|
470
|
+
terminal IdTerminal: /[_]*[a-zA-Z][-\w]*/;
|
package/src/likec4lib.ts
ADDED
|
@@ -17,10 +17,11 @@ export class LikeC4DocumentSymbolProvider implements DocumentSymbolProvider {
|
|
|
17
17
|
|
|
18
18
|
getSymbols({
|
|
19
19
|
parseResult: {
|
|
20
|
-
value: { specifications, models, views }
|
|
20
|
+
value: { specifications, models, views, likec4lib }
|
|
21
21
|
}
|
|
22
22
|
}: LikeC4LangiumDocument): MaybePromise<DocumentSymbol[]> {
|
|
23
23
|
return [
|
|
24
|
+
...likec4lib.map(l => () => this.getLikec4LibSymbol(l)),
|
|
24
25
|
...specifications.map(s => () => this.getSpecSymbol(s)),
|
|
25
26
|
...models.map(s => () => this.getModelSymbol(s)),
|
|
26
27
|
...views.map(s => () => this.getModelViewsSymbol(s))
|
|
@@ -34,6 +35,22 @@ export class LikeC4DocumentSymbolProvider implements DocumentSymbolProvider {
|
|
|
34
35
|
})
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
protected getLikec4LibSymbol(astLib: ast.LikeC4Lib): DocumentSymbol[] {
|
|
39
|
+
const cstModel = astLib?.$cstNode
|
|
40
|
+
if (!cstModel) return []
|
|
41
|
+
const children = astLib.icons.map(i => this.getLibIconSymbol(i)).filter(isTruthy)
|
|
42
|
+
if (children.length === 0) return []
|
|
43
|
+
return [
|
|
44
|
+
{
|
|
45
|
+
kind: SymbolKind.Namespace,
|
|
46
|
+
name: 'icons',
|
|
47
|
+
range: cstModel.range,
|
|
48
|
+
selectionRange: GrammarUtils.findNodeForKeyword(cstModel, 'icons')?.range ?? cstModel.range,
|
|
49
|
+
children
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
|
|
37
54
|
protected getSpecSymbol(astSpec: ast.SpecificationRule): DocumentSymbol[] {
|
|
38
55
|
const cstModel = astSpec?.$cstNode
|
|
39
56
|
if (!cstModel) return []
|
|
@@ -179,6 +196,16 @@ export class LikeC4DocumentSymbolProvider implements DocumentSymbolProvider {
|
|
|
179
196
|
}
|
|
180
197
|
}
|
|
181
198
|
|
|
199
|
+
protected getLibIconSymbol(astTag: ast.LibIcon): DocumentSymbol | null {
|
|
200
|
+
if (!astTag.$cstNode || isEmpty(astTag.name)) return null
|
|
201
|
+
return {
|
|
202
|
+
kind: this.symbolKind(astTag),
|
|
203
|
+
name: astTag.name,
|
|
204
|
+
range: astTag.$cstNode.range,
|
|
205
|
+
selectionRange: astTag.$cstNode.range
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
182
209
|
protected getViewSymbol(astView: ast.LikeC4View): DocumentSymbol[] {
|
|
183
210
|
const cst = astView?.$cstNode
|
|
184
211
|
if (!cst) return []
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AstNode } from 'langium'
|
|
2
2
|
import { AbstractSemanticTokenProvider, type SemanticTokenAcceptor } from 'langium/lsp'
|
|
3
|
+
import { isTruthy } from 'remeda'
|
|
3
4
|
import { SemanticTokenModifiers, SemanticTokenTypes } from 'vscode-languageserver-protocol'
|
|
4
5
|
import { ast } from '../ast'
|
|
5
6
|
|
|
@@ -16,8 +17,18 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
|
|
|
16
17
|
type: SemanticTokenTypes.function
|
|
17
18
|
})
|
|
18
19
|
}
|
|
20
|
+
if (ast.isLibIcon(node)) {
|
|
21
|
+
acceptor({
|
|
22
|
+
node,
|
|
23
|
+
property: 'name',
|
|
24
|
+
type: SemanticTokenTypes.type,
|
|
25
|
+
modifier: [SemanticTokenModifiers.definition]
|
|
26
|
+
})
|
|
27
|
+
return 'prune'
|
|
28
|
+
}
|
|
29
|
+
|
|
19
30
|
if (ast.isRelation(node) && 'kind' in node) {
|
|
20
|
-
|
|
31
|
+
acceptor({
|
|
21
32
|
node,
|
|
22
33
|
property: 'kind',
|
|
23
34
|
type: SemanticTokenTypes.function
|
|
@@ -40,7 +51,7 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
|
|
|
40
51
|
})
|
|
41
52
|
return 'prune'
|
|
42
53
|
}
|
|
43
|
-
if ((ast.
|
|
54
|
+
if ((ast.isElementDescedantsExpression(node) || ast.isWildcardExpression(node)) && node.$cstNode) {
|
|
44
55
|
acceptor({
|
|
45
56
|
cst: node.$cstNode,
|
|
46
57
|
type: SemanticTokenTypes.variable,
|
|
@@ -51,7 +62,7 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
|
|
|
51
62
|
})
|
|
52
63
|
return 'prune'
|
|
53
64
|
}
|
|
54
|
-
if (ast.
|
|
65
|
+
if (ast.isElementKindExpression(node) && isTruthy(node.kind)) {
|
|
55
66
|
acceptor({
|
|
56
67
|
node,
|
|
57
68
|
property: 'kind',
|
|
@@ -59,7 +70,7 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
|
|
|
59
70
|
modifier: [SemanticTokenModifiers.definition]
|
|
60
71
|
})
|
|
61
72
|
}
|
|
62
|
-
if (ast.
|
|
73
|
+
if (ast.isElementTagExpression(node) && isTruthy(node.tag)) {
|
|
63
74
|
acceptor({
|
|
64
75
|
node,
|
|
65
76
|
property: 'tag',
|
|
@@ -158,11 +169,21 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
|
|
|
158
169
|
property: 'key',
|
|
159
170
|
type: SemanticTokenTypes.property
|
|
160
171
|
})
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
172
|
+
if (ast.isIconProperty(node) && node.libicon) {
|
|
173
|
+
acceptor({
|
|
174
|
+
node,
|
|
175
|
+
property: 'libicon',
|
|
176
|
+
type: SemanticTokenTypes.enum
|
|
177
|
+
})
|
|
178
|
+
}
|
|
179
|
+
if ('value' in node) {
|
|
180
|
+
acceptor({
|
|
181
|
+
node,
|
|
182
|
+
property: 'value',
|
|
183
|
+
type: SemanticTokenTypes.string
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
|
|
166
187
|
return 'prune'
|
|
167
188
|
}
|
|
168
189
|
if (ast.isElement(node)) {
|
|
@@ -145,11 +145,20 @@ export class LikeC4ModelParser {
|
|
|
145
145
|
const bodyProps = mapToObj(astNode.body?.props.filter(ast.isElementStringProperty) ?? [], p => [p.key, p.value])
|
|
146
146
|
|
|
147
147
|
title = toSingleLine(title ?? bodyProps.title)
|
|
148
|
-
description = removeIndent(description ??
|
|
149
|
-
technology = toSingleLine(technology ??
|
|
148
|
+
description = removeIndent(bodyProps.description ?? description)
|
|
149
|
+
technology = toSingleLine(bodyProps.technology ?? technology)
|
|
150
150
|
|
|
151
151
|
const links = astNode.body?.props.filter(ast.isLinkProperty).map(p => p.value)
|
|
152
152
|
|
|
153
|
+
// Property has higher priority than from style
|
|
154
|
+
const iconProp = astNode.body?.props.find(ast.isIconProperty)
|
|
155
|
+
if (iconProp) {
|
|
156
|
+
const value = iconProp.libicon?.ref?.name ?? iconProp.value
|
|
157
|
+
if (isTruthy(value)) {
|
|
158
|
+
style.icon = value as c4.IconUrl
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
153
162
|
return {
|
|
154
163
|
id,
|
|
155
164
|
kind,
|
|
@@ -209,39 +218,70 @@ export class LikeC4ModelParser {
|
|
|
209
218
|
}
|
|
210
219
|
}
|
|
211
220
|
|
|
212
|
-
|
|
213
|
-
|
|
221
|
+
// TODO validate view rules
|
|
222
|
+
private parseViewRulePredicate(astNode: ast.ViewRulePredicate, _isValid: IsValidFn): c4.ViewRulePredicate {
|
|
223
|
+
const exprs = [] as c4.Expression[]
|
|
224
|
+
let exprNode: ast.Expressions | undefined = astNode.exprs
|
|
225
|
+
while (exprNode) {
|
|
226
|
+
try {
|
|
227
|
+
if (isTruthy(exprNode.value)) {
|
|
228
|
+
exprs.unshift(this.parseExpression(exprNode.value))
|
|
229
|
+
}
|
|
230
|
+
} catch (e) {
|
|
231
|
+
logWarnError(e)
|
|
232
|
+
}
|
|
233
|
+
exprNode = exprNode.prev
|
|
234
|
+
}
|
|
235
|
+
return ast.isIncludePredicate(astNode) ? { include: exprs } : { exclude: exprs }
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private parseElementExpressionsIterator(astNode: ast.ElementExpressionsIterator): c4.ElementExpression[] {
|
|
239
|
+
const exprs = [] as c4.ElementExpression[]
|
|
240
|
+
let iter: ast.ElementExpressionsIterator | undefined = astNode
|
|
241
|
+
while (iter) {
|
|
242
|
+
try {
|
|
243
|
+
exprs.unshift(this.parseElementExpr(iter.value))
|
|
244
|
+
} catch (e) {
|
|
245
|
+
logWarnError(e)
|
|
246
|
+
}
|
|
247
|
+
iter = iter.prev
|
|
248
|
+
}
|
|
249
|
+
return exprs
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
private parseElementExpr(astNode: ast.ElementExpression): c4.ElementExpression {
|
|
253
|
+
if (ast.isWildcardExpression(astNode)) {
|
|
214
254
|
return {
|
|
215
255
|
wildcard: true
|
|
216
256
|
}
|
|
217
257
|
}
|
|
218
|
-
if (ast.
|
|
219
|
-
|
|
258
|
+
if (ast.isElementKindExpression(astNode)) {
|
|
259
|
+
invariant(astNode.kind, 'ElementKindExpr kind is not resolved: ' + astNode.$cstNode?.text)
|
|
220
260
|
return {
|
|
221
261
|
elementKind: astNode.kind.$refText as c4.ElementKind,
|
|
222
262
|
isEqual: astNode.isEqual
|
|
223
263
|
}
|
|
224
264
|
}
|
|
225
|
-
if (ast.
|
|
265
|
+
if (ast.isElementTagExpression(astNode)) {
|
|
266
|
+
invariant(astNode.tag, 'ElementTagExpr tag is not resolved: ' + astNode.$cstNode?.text)
|
|
226
267
|
let elementTag = astNode.tag.$refText
|
|
227
268
|
if (elementTag.startsWith('#')) {
|
|
228
269
|
elementTag = elementTag.slice(1)
|
|
229
270
|
}
|
|
230
|
-
// invariant(astNode.tag.ref, 'ElementTagExpr tag is not resolved: ' + astNode.$cstNode?.text)
|
|
231
271
|
return {
|
|
232
272
|
elementTag: elementTag as c4.Tag,
|
|
233
273
|
isEqual: astNode.isEqual
|
|
234
274
|
}
|
|
235
275
|
}
|
|
236
|
-
if (ast.
|
|
237
|
-
const elementNode = elementRef(astNode.
|
|
238
|
-
invariant(elementNode, 'Element not found ' + astNode.
|
|
276
|
+
if (ast.isExpandElementExpression(astNode)) {
|
|
277
|
+
const elementNode = elementRef(astNode.expand)
|
|
278
|
+
invariant(elementNode, 'Element not found ' + astNode.expand.$cstNode?.text)
|
|
239
279
|
const expanded = this.resolveFqn(elementNode)
|
|
240
280
|
return {
|
|
241
281
|
expanded
|
|
242
282
|
}
|
|
243
283
|
}
|
|
244
|
-
if (ast.
|
|
284
|
+
if (ast.isElementDescedantsExpression(astNode)) {
|
|
245
285
|
const elementNode = elementRef(astNode.parent)
|
|
246
286
|
invariant(elementNode, 'Element not found ' + astNode.parent.$cstNode?.text)
|
|
247
287
|
const element = this.resolveFqn(elementNode)
|
|
@@ -261,20 +301,25 @@ export class LikeC4ModelParser {
|
|
|
261
301
|
nonexhaustive(astNode)
|
|
262
302
|
}
|
|
263
303
|
|
|
264
|
-
private parseCustomElementExpr(astNode: ast.
|
|
304
|
+
private parseCustomElementExpr(astNode: ast.CustomElementExpression): c4.CustomElementExpr {
|
|
265
305
|
let targetRef
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
306
|
+
switch (true) {
|
|
307
|
+
case ast.isElementRef(astNode.target):
|
|
308
|
+
targetRef = astNode.target
|
|
309
|
+
break
|
|
310
|
+
case ast.isExpandElementExpression(astNode.target):
|
|
311
|
+
targetRef = astNode.target.expand
|
|
312
|
+
break
|
|
313
|
+
case ast.isElementDescedantsExpression(astNode.target):
|
|
314
|
+
targetRef = astNode.target.parent
|
|
315
|
+
break
|
|
316
|
+
default:
|
|
317
|
+
throw new Error('Unsupported target of custom element')
|
|
318
|
+
}
|
|
274
319
|
const elementNode = elementRef(targetRef)
|
|
275
320
|
invariant(elementNode, 'element not found: ' + astNode.$cstNode?.text)
|
|
276
321
|
const element = this.resolveFqn(elementNode)
|
|
277
|
-
const props = astNode.
|
|
322
|
+
const props = astNode.custom.props ?? []
|
|
278
323
|
return props.reduce(
|
|
279
324
|
(acc, prop) => {
|
|
280
325
|
if (ast.isNavigateToProperty(prop)) {
|
|
@@ -290,7 +335,10 @@ export class LikeC4ModelParser {
|
|
|
290
335
|
return acc
|
|
291
336
|
}
|
|
292
337
|
if (ast.isIconProperty(prop)) {
|
|
293
|
-
|
|
338
|
+
const value = prop.libicon?.ref?.name ?? prop.value
|
|
339
|
+
if (isTruthy(value)) {
|
|
340
|
+
acc.custom[prop.key] = value as c4.IconUrl
|
|
341
|
+
}
|
|
294
342
|
return acc
|
|
295
343
|
}
|
|
296
344
|
if (ast.isColorProperty(prop)) {
|
|
@@ -320,40 +368,25 @@ export class LikeC4ModelParser {
|
|
|
320
368
|
)
|
|
321
369
|
}
|
|
322
370
|
|
|
323
|
-
private
|
|
324
|
-
if (ast.
|
|
371
|
+
private parseExpression(astNode: ast.Expression): c4.Expression {
|
|
372
|
+
if (ast.isCustomRelationExpression(astNode)) {
|
|
325
373
|
return this.parseCustomRelationExpr(astNode)
|
|
326
374
|
}
|
|
327
|
-
if (ast.
|
|
328
|
-
return this.parseRelationExpr(astNode)
|
|
329
|
-
}
|
|
330
|
-
if (ast.isInOutExpr(astNode)) {
|
|
331
|
-
return {
|
|
332
|
-
inout: this.parseElementExpr(astNode.inout.to)
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
if (ast.isOutgoingExpr(astNode)) {
|
|
336
|
-
return {
|
|
337
|
-
outgoing: this.parseElementExpr(astNode.from)
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
if (ast.isIncomingExpr(astNode)) {
|
|
341
|
-
return {
|
|
342
|
-
incoming: this.parseElementExpr(astNode.to)
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
if (ast.isCustomElementExpr(astNode)) {
|
|
375
|
+
if (ast.isCustomElementExpression(astNode)) {
|
|
346
376
|
return this.parseCustomElementExpr(astNode)
|
|
347
377
|
}
|
|
348
|
-
if (ast.
|
|
378
|
+
if (ast.isElementExpression(astNode)) {
|
|
349
379
|
return this.parseElementExpr(astNode)
|
|
350
380
|
}
|
|
381
|
+
if (ast.isRelationExpression(astNode)) {
|
|
382
|
+
return this.parseRelationExpr(astNode)
|
|
383
|
+
}
|
|
351
384
|
nonexhaustive(astNode)
|
|
352
385
|
}
|
|
353
386
|
|
|
354
|
-
private parseCustomRelationExpr(astNode: ast.
|
|
387
|
+
private parseCustomRelationExpr(astNode: ast.CustomRelationExpression): c4.CustomRelationExpr {
|
|
355
388
|
const relation = this.parseRelationExpr(astNode.relation)
|
|
356
|
-
const props = astNode.
|
|
389
|
+
const props = astNode.custom.props ?? []
|
|
357
390
|
return props.reduce(
|
|
358
391
|
(acc, prop) => {
|
|
359
392
|
if (ast.isRelationStringProperty(prop)) {
|
|
@@ -385,30 +418,40 @@ export class LikeC4ModelParser {
|
|
|
385
418
|
)
|
|
386
419
|
}
|
|
387
420
|
|
|
388
|
-
private parseRelationExpr(astNode: ast.
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
421
|
+
private parseRelationExpr(astNode: ast.RelationExpression): c4.RelationExpression {
|
|
422
|
+
if (ast.isDirectedRelationExpression(astNode)) {
|
|
423
|
+
return {
|
|
424
|
+
source: this.parseElementExpr(astNode.source.from),
|
|
425
|
+
target: this.parseElementExpr(astNode.target),
|
|
426
|
+
isBidirectional: astNode.source.isBidirectional
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
if (ast.isInOutRelationExpression(astNode)) {
|
|
430
|
+
return {
|
|
431
|
+
inout: this.parseElementExpr(astNode.inout.to)
|
|
432
|
+
}
|
|
393
433
|
}
|
|
434
|
+
if (ast.isOutgoingRelationExpression(astNode)) {
|
|
435
|
+
return {
|
|
436
|
+
outgoing: this.parseElementExpr(astNode.from)
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
if (ast.isIncomingRelationExpression(astNode)) {
|
|
440
|
+
return {
|
|
441
|
+
incoming: this.parseElementExpr(astNode.to)
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
nonexhaustive(astNode)
|
|
394
445
|
}
|
|
395
446
|
|
|
396
447
|
private parseViewRule(astRule: ast.ViewRule, isValid: IsValidFn): c4.ViewRule {
|
|
397
|
-
if (ast.
|
|
398
|
-
|
|
399
|
-
try {
|
|
400
|
-
return isValid(n) ? this.parsePredicateExpr(n) : []
|
|
401
|
-
} catch (e) {
|
|
402
|
-
logWarnError(e)
|
|
403
|
-
return []
|
|
404
|
-
}
|
|
405
|
-
})
|
|
406
|
-
return ast.isIncludePredicate(astRule) ? { include: exprs } : { exclude: exprs }
|
|
448
|
+
if (ast.isViewRulePredicate(astRule)) {
|
|
449
|
+
return this.parseViewRulePredicate(astRule, isValid)
|
|
407
450
|
}
|
|
408
451
|
if (ast.isViewRuleStyle(astRule)) {
|
|
409
|
-
const styleProps = toElementStyle(astRule.
|
|
452
|
+
const styleProps = toElementStyle(astRule.props)
|
|
410
453
|
return {
|
|
411
|
-
targets:
|
|
454
|
+
targets: this.parseElementExpressionsIterator(astRule.target),
|
|
412
455
|
style: {
|
|
413
456
|
...styleProps
|
|
414
457
|
}
|
|
@@ -563,15 +606,24 @@ export class LikeC4ModelParser {
|
|
|
563
606
|
try {
|
|
564
607
|
if (ast.isDynamicViewRulePredicate(n)) {
|
|
565
608
|
const include = [] as (c4.ElementExpression | c4.CustomElementExpr)[]
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
609
|
+
let iter: ast.DynamicViewRulePredicateIterator | undefined = n.exprs
|
|
610
|
+
while (iter) {
|
|
611
|
+
try {
|
|
612
|
+
switch (true) {
|
|
613
|
+
case ast.isElementExpression(iter.value):
|
|
614
|
+
isValid(iter.value) && include.unshift(this.parseElementExpr(iter.value))
|
|
615
|
+
break
|
|
616
|
+
|
|
617
|
+
case ast.isCustomElementExpression(iter.value):
|
|
618
|
+
isValid(iter.value) && include.unshift(this.parseCustomElementExpr(iter.value))
|
|
619
|
+
break
|
|
620
|
+
default:
|
|
621
|
+
nonexhaustive(iter.value)
|
|
622
|
+
}
|
|
623
|
+
} catch (e) {
|
|
624
|
+
logWarnError(e)
|
|
574
625
|
}
|
|
626
|
+
iter = iter.prev
|
|
575
627
|
}
|
|
576
628
|
if (include.length > 0) {
|
|
577
629
|
acc.push({ include })
|
|
@@ -579,8 +631,8 @@ export class LikeC4ModelParser {
|
|
|
579
631
|
return acc
|
|
580
632
|
}
|
|
581
633
|
if (ast.isViewRuleStyle(n)) {
|
|
582
|
-
const styleProps = toElementStyle(n.
|
|
583
|
-
const targets =
|
|
634
|
+
const styleProps = toElementStyle(n.props)
|
|
635
|
+
const targets = this.parseElementExpressionsIterator(n.target)
|
|
584
636
|
if (targets.length > 0) {
|
|
585
637
|
acc.push({
|
|
586
638
|
targets,
|