@likec4/language-server 1.14.0 → 1.15.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/dist/browser.cjs +1 -1
- package/dist/browser.d.cts +2 -2
- package/dist/browser.d.mts +2 -2
- package/dist/browser.d.ts +2 -2
- package/dist/browser.mjs +2 -2
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/likec4lib.cjs +0 -1
- package/dist/likec4lib.mjs +0 -1
- package/dist/model-graph/index.cjs +1 -1
- package/dist/model-graph/index.mjs +1 -1
- package/dist/shared/{language-server.CbDa016p.cjs → language-server.B8s2wfT_.cjs} +279 -68
- package/dist/shared/{language-server.DFLaUdYu.mjs → language-server.BT4WTbFI.mjs} +281 -70
- package/dist/shared/{language-server.DZYziEuF.mjs → language-server.BuChFlda.mjs} +231 -44
- package/dist/shared/{language-server.CO6aEDQm.d.mts → language-server.DfMwkd2l.d.mts} +46 -15
- package/dist/shared/{language-server.CITj0XN3.cjs → language-server.DfjkvknB.cjs} +230 -43
- package/dist/shared/{language-server.Cqyh6-9S.d.cts → language-server.U2piOAVt.d.cts} +46 -15
- package/dist/shared/{language-server.BI99piRy.d.ts → language-server.j-ShR6as.d.ts} +46 -15
- package/package.json +10 -10
- package/src/ast.ts +1 -0
- package/src/formatting/LikeC4Formatter.ts +44 -4
- package/src/generated/ast.ts +103 -19
- package/src/generated/grammar.ts +1 -1
- package/src/generated-lib/icons.ts +0 -1
- package/src/like-c4.langium +32 -9
- package/src/lsp/CompletionProvider.ts +70 -2
- package/src/lsp/SemanticTokenProvider.ts +3 -1
- package/src/model/model-builder.ts +13 -7
- package/src/model/model-parser.ts +71 -20
- package/src/model-graph/compute-view/__test__/fixture.ts +96 -29
- package/src/model-graph/compute-view/compute.ts +223 -40
- package/src/model-graph/compute-view/predicates.ts +12 -6
- package/src/model-graph/utils/applyCustomElementProperties.ts +18 -4
- package/src/model-graph/utils/applyCustomRelationProperties.ts +9 -39
- package/src/model-graph/utils/applyViewRuleStyles.ts +30 -25
- package/src/model-graph/utils/buildComputeNodes.ts +69 -17
- package/src/model-graph/utils/elementExpressionToPredicate.ts +3 -1
- package/src/model-graph/utils/relationExpressionToPredicates.ts +43 -0
- package/src/model-graph/utils/sortNodes.ts +11 -7
- package/src/references/scope-computation.ts +3 -2
- package/src/validation/index.ts +2 -2
- package/src/validation/specification.ts +4 -4
package/src/like-c4.langium
CHANGED
|
@@ -197,7 +197,7 @@ MetadataAttribute:
|
|
|
197
197
|
ModelViews:
|
|
198
198
|
name='views' '{' (
|
|
199
199
|
views+=LikeC4ViewRule |
|
|
200
|
-
styles+=
|
|
200
|
+
styles+=ViewRuleStyleOrGlobalRef
|
|
201
201
|
)*
|
|
202
202
|
'}';
|
|
203
203
|
|
|
@@ -267,15 +267,27 @@ ViewLayoutDirection returns string:
|
|
|
267
267
|
|
|
268
268
|
ViewRule:
|
|
269
269
|
ViewRulePredicate |
|
|
270
|
-
|
|
271
|
-
|
|
270
|
+
ViewRuleGroup |
|
|
271
|
+
ViewRuleStyleOrGlobalRef |
|
|
272
272
|
ViewRuleAutoLayout
|
|
273
273
|
;
|
|
274
274
|
|
|
275
|
+
ViewRuleGroup:
|
|
276
|
+
'group' title=String? '{'
|
|
277
|
+
props+=(
|
|
278
|
+
ColorProperty |
|
|
279
|
+
BorderProperty |
|
|
280
|
+
OpacityProperty
|
|
281
|
+
)*
|
|
282
|
+
groupRules+=(
|
|
283
|
+
ViewRulePredicate |
|
|
284
|
+
ViewRuleGroup
|
|
285
|
+
)*
|
|
286
|
+
'}';
|
|
287
|
+
|
|
275
288
|
DynamicViewRule:
|
|
276
289
|
DynamicViewIncludePredicate |
|
|
277
|
-
|
|
278
|
-
ViewRuleGlobalStyle |
|
|
290
|
+
ViewRuleStyleOrGlobalRef |
|
|
279
291
|
ViewRuleAutoLayout
|
|
280
292
|
;
|
|
281
293
|
|
|
@@ -444,7 +456,10 @@ ViewRuleStyle:
|
|
|
444
456
|
'}';
|
|
445
457
|
|
|
446
458
|
ViewRuleGlobalStyle:
|
|
447
|
-
'global' 'style' style=[
|
|
459
|
+
'global' 'style' style=[GlobalStyleId];
|
|
460
|
+
|
|
461
|
+
ViewRuleStyleOrGlobalRef:
|
|
462
|
+
ViewRuleStyle | ViewRuleGlobalStyle;
|
|
448
463
|
|
|
449
464
|
ViewRuleAutoLayout:
|
|
450
465
|
'autoLayout' direction=ViewLayoutDirection (
|
|
@@ -491,18 +506,26 @@ RelationNavigateToProperty:
|
|
|
491
506
|
Globals:
|
|
492
507
|
name='global' '{'
|
|
493
508
|
(
|
|
494
|
-
styles+=GlobalStyle*
|
|
509
|
+
styles+=(GlobalStyle | GlobalStyleGroup)*
|
|
495
510
|
)*
|
|
496
511
|
'}';
|
|
497
512
|
|
|
513
|
+
GlobalStyleId:
|
|
514
|
+
name=IdTerminal;
|
|
515
|
+
|
|
498
516
|
GlobalStyle:
|
|
499
|
-
'style'
|
|
517
|
+
'style' id=GlobalStyleId target=ElementExpressionsIterator '{'
|
|
500
518
|
props+=(
|
|
501
519
|
StyleProperty |
|
|
502
520
|
NotationProperty
|
|
503
521
|
)*
|
|
504
522
|
'}';
|
|
505
523
|
|
|
524
|
+
GlobalStyleGroup:
|
|
525
|
+
'styleGroup' id=GlobalStyleId '{'
|
|
526
|
+
styles+=ViewRuleStyle*
|
|
527
|
+
'}';
|
|
528
|
+
|
|
506
529
|
// Common properties -------------------------------------
|
|
507
530
|
|
|
508
531
|
LinkProperty:
|
|
@@ -593,7 +616,7 @@ CustomColorId returns string:
|
|
|
593
616
|
IdTerminal | ElementShape | ArrowType | LineOptions | 'element' | 'model';
|
|
594
617
|
|
|
595
618
|
Id returns string:
|
|
596
|
-
IdTerminal | ElementShape | ThemeColor | ArrowType | LineOptions | 'element' | 'model';
|
|
619
|
+
IdTerminal | ElementShape | ThemeColor | ArrowType | LineOptions | 'element' | 'model' | 'group';
|
|
597
620
|
|
|
598
621
|
fragment EqOperator:
|
|
599
622
|
(
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { type GrammarAST, type MaybePromise } from 'langium'
|
|
1
|
+
import { AstUtils, type GrammarAST, type MaybePromise } from 'langium'
|
|
2
2
|
import {
|
|
3
3
|
type CompletionAcceptor,
|
|
4
4
|
type CompletionContext,
|
|
5
5
|
type CompletionProviderOptions,
|
|
6
6
|
DefaultCompletionProvider
|
|
7
7
|
} from 'langium/lsp'
|
|
8
|
+
import { anyPass } from 'remeda'
|
|
8
9
|
import { CompletionItemKind, InsertTextFormat } from 'vscode-languageserver-types'
|
|
10
|
+
import { ast } from '../ast'
|
|
9
11
|
|
|
10
12
|
export class LikeC4CompletionProvider extends DefaultCompletionProvider {
|
|
11
13
|
override readonly completionOptions = {
|
|
@@ -20,7 +22,15 @@ export class LikeC4CompletionProvider extends DefaultCompletionProvider {
|
|
|
20
22
|
if (!this.filterKeyword(context, keyword)) {
|
|
21
23
|
return
|
|
22
24
|
}
|
|
23
|
-
if (['
|
|
25
|
+
if (['title', 'description', 'technology'].includes(keyword.value)) {
|
|
26
|
+
return acceptor(context, {
|
|
27
|
+
label: keyword.value,
|
|
28
|
+
kind: CompletionItemKind.Property,
|
|
29
|
+
insertTextFormat: InsertTextFormat.Snippet,
|
|
30
|
+
insertText: `${keyword.value} '\${0}'`
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
if (['views', 'specification', 'model', 'with'].includes(keyword.value)) {
|
|
24
34
|
return acceptor(context, {
|
|
25
35
|
label: keyword.value,
|
|
26
36
|
detail: `Insert ${keyword.value} block`,
|
|
@@ -29,6 +39,19 @@ export class LikeC4CompletionProvider extends DefaultCompletionProvider {
|
|
|
29
39
|
insertText: `${keyword.value} {\n\t$0\n}`
|
|
30
40
|
})
|
|
31
41
|
}
|
|
42
|
+
if (keyword.value === 'group') {
|
|
43
|
+
return acceptor(context, {
|
|
44
|
+
label: keyword.value,
|
|
45
|
+
detail: `Insert group block`,
|
|
46
|
+
kind: CompletionItemKind.Class,
|
|
47
|
+
insertTextFormat: InsertTextFormat.Snippet,
|
|
48
|
+
insertText: [
|
|
49
|
+
'group \'${1:Title}\' {',
|
|
50
|
+
'\t$0',
|
|
51
|
+
'}'
|
|
52
|
+
].join('\n')
|
|
53
|
+
})
|
|
54
|
+
}
|
|
32
55
|
if (keyword.value === 'dynamic') {
|
|
33
56
|
return acceptor(context, {
|
|
34
57
|
label: keyword.value,
|
|
@@ -44,6 +67,51 @@ export class LikeC4CompletionProvider extends DefaultCompletionProvider {
|
|
|
44
67
|
].join('\n')
|
|
45
68
|
})
|
|
46
69
|
}
|
|
70
|
+
if (keyword.value === 'style' && context.node) {
|
|
71
|
+
if (AstUtils.hasContainerOfType(context.node, ast.isGlobalStyle)) {
|
|
72
|
+
return acceptor(context, {
|
|
73
|
+
label: keyword.value,
|
|
74
|
+
detail: `Insert ${keyword.value} block`,
|
|
75
|
+
kind: CompletionItemKind.Module,
|
|
76
|
+
insertTextFormat: InsertTextFormat.Snippet,
|
|
77
|
+
insertText: `${keyword.value} \${1:name} \${2:*} {\n\t\${3|color,shape,border,opacity,icon|} \$0\n}`
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
if (AstUtils.hasContainerOfType(context.node, anyPass([ast.isModelViews, ast.isGlobalStyleGroup]))) {
|
|
81
|
+
return acceptor(context, {
|
|
82
|
+
label: keyword.value,
|
|
83
|
+
detail: `Insert ${keyword.value} block`,
|
|
84
|
+
kind: CompletionItemKind.Module,
|
|
85
|
+
insertTextFormat: InsertTextFormat.Snippet,
|
|
86
|
+
insertText: `${keyword.value} \${1:*} {\n\t\${2|color,shape,border,opacity,icon|} \$0\n}`
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
return acceptor(context, {
|
|
90
|
+
label: keyword.value,
|
|
91
|
+
detail: `Insert ${keyword.value} block`,
|
|
92
|
+
kind: CompletionItemKind.Module,
|
|
93
|
+
insertTextFormat: InsertTextFormat.Snippet,
|
|
94
|
+
insertText: `${keyword.value} {\n\t\${1|color,shape,border,opacity,icon|} \$0\n}`
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
if (keyword.value === 'extend') {
|
|
98
|
+
return acceptor(context, {
|
|
99
|
+
label: keyword.value,
|
|
100
|
+
detail: `Extend another view`,
|
|
101
|
+
kind: CompletionItemKind.Class,
|
|
102
|
+
insertTextFormat: InsertTextFormat.Snippet,
|
|
103
|
+
insertText: 'extend ${1:element} {\n\t$0\n}'
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (keyword.value === 'autoLayout') {
|
|
108
|
+
return acceptor(context, {
|
|
109
|
+
label: keyword.value,
|
|
110
|
+
kind: CompletionItemKind.Class,
|
|
111
|
+
insertTextFormat: InsertTextFormat.Snippet,
|
|
112
|
+
insertText: 'autoLayout ${1|TopBottom,BottomTop,LeftRight,RightLeft|}$0'
|
|
113
|
+
})
|
|
114
|
+
}
|
|
47
115
|
acceptor(context, {
|
|
48
116
|
label: keyword.value,
|
|
49
117
|
kind: this.getKeywordCompletionItemKind(keyword),
|
|
@@ -68,7 +68,7 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
|
|
|
68
68
|
SemanticTokenModifiers.readonly
|
|
69
69
|
]
|
|
70
70
|
})
|
|
71
|
-
return
|
|
71
|
+
return 'prune'
|
|
72
72
|
}
|
|
73
73
|
if (ast.isWhereRelationKind(node) && isTruthy(node.value)) {
|
|
74
74
|
acceptor({
|
|
@@ -97,6 +97,7 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
|
|
|
97
97
|
type: SemanticTokenTypes.type,
|
|
98
98
|
modifier: [SemanticTokenModifiers.definition]
|
|
99
99
|
})
|
|
100
|
+
return 'prune'
|
|
100
101
|
}
|
|
101
102
|
if (ast.isElementTagExpression(node) && isTruthy(node.tag)) {
|
|
102
103
|
acceptor({
|
|
@@ -105,6 +106,7 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
|
|
|
105
106
|
type: SemanticTokenTypes.type,
|
|
106
107
|
modifier: [SemanticTokenModifiers.definition]
|
|
107
108
|
})
|
|
109
|
+
return 'prune'
|
|
108
110
|
}
|
|
109
111
|
if (ast.isElementRef(node) || ast.isFqnElementRef(node)) {
|
|
110
112
|
acceptor({
|
|
@@ -46,7 +46,6 @@ import { isParsedLikeC4LangiumDocument } from '../ast'
|
|
|
46
46
|
import { logError, logger, logWarnError } from '../logger'
|
|
47
47
|
import { computeDynamicView, computeView, LikeC4ModelGraph } from '../model-graph'
|
|
48
48
|
import type { LikeC4Services } from '../module'
|
|
49
|
-
import { printDocs } from '../utils/printDocs'
|
|
50
49
|
import { assignNavigateTo, resolveGlobalRules, resolveRelativePaths, resolveRulesExtendedViews } from '../view-utils'
|
|
51
50
|
|
|
52
51
|
function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[]): c4.ParsedLikeC4Model {
|
|
@@ -256,18 +255,23 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
256
255
|
|
|
257
256
|
return {
|
|
258
257
|
...model,
|
|
258
|
+
customColorDefinitions,
|
|
259
259
|
tags,
|
|
260
260
|
links,
|
|
261
261
|
docUri,
|
|
262
262
|
description,
|
|
263
263
|
title,
|
|
264
|
-
id
|
|
265
|
-
customColorDefinitions
|
|
264
|
+
id
|
|
266
265
|
}
|
|
267
266
|
}
|
|
268
267
|
}
|
|
269
268
|
|
|
270
|
-
const parsedViews =
|
|
269
|
+
const parsedViews = pipe(
|
|
270
|
+
docs,
|
|
271
|
+
flatMap(d => map(d.c4Views, toC4View(d))),
|
|
272
|
+
// Resolve relative paths and sort by
|
|
273
|
+
resolveRelativePaths
|
|
274
|
+
)
|
|
271
275
|
// Add index view if not present
|
|
272
276
|
if (!parsedViews.some(v => v.id === 'index')) {
|
|
273
277
|
parsedViews.unshift({
|
|
@@ -292,7 +296,6 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
292
296
|
|
|
293
297
|
const views = pipe(
|
|
294
298
|
parsedViews,
|
|
295
|
-
resolveRelativePaths,
|
|
296
299
|
indexBy(prop('id')),
|
|
297
300
|
resolveRulesExtendedViews
|
|
298
301
|
)
|
|
@@ -363,7 +366,7 @@ export class LikeC4ModelBuilder {
|
|
|
363
366
|
logger.debug('[ModelBuilder] No documents to build model from')
|
|
364
367
|
return null
|
|
365
368
|
}
|
|
366
|
-
logger.debug(`[ModelBuilder]
|
|
369
|
+
logger.debug(`[ModelBuilder] buildModel (${docs.length} docs)`)
|
|
367
370
|
return buildModel(this.services, docs)
|
|
368
371
|
})
|
|
369
372
|
}
|
|
@@ -396,7 +399,10 @@ export class LikeC4ModelBuilder {
|
|
|
396
399
|
|
|
397
400
|
const allViews = [] as c4.ComputedView[]
|
|
398
401
|
for (const view of values(model.views)) {
|
|
399
|
-
const
|
|
402
|
+
const resolvedView = resolveGlobalRules(view, model.globals.styles)
|
|
403
|
+
const result = isElementView(resolvedView)
|
|
404
|
+
? computeView(resolvedView, index)
|
|
405
|
+
: computeDynamicView(resolvedView, index)
|
|
400
406
|
if (!result.isSuccess) {
|
|
401
407
|
logWarnError(result.error)
|
|
402
408
|
continue
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
ViewOps
|
|
31
31
|
} from '../ast'
|
|
32
32
|
import { elementRef, getFqnElementRef } from '../elementRef'
|
|
33
|
-
import { type NotationProperty } from '../generated/ast'
|
|
33
|
+
import { isGlobalStyle, isGlobalStyleGroup, type NotationProperty } from '../generated/ast'
|
|
34
34
|
import { logError, logger, logWarnError } from '../logger'
|
|
35
35
|
import type { LikeC4Services } from '../module'
|
|
36
36
|
import { stringHash } from '../utils'
|
|
@@ -291,7 +291,7 @@ export class LikeC4ModelParser {
|
|
|
291
291
|
const styles = globals.flatMap(r => r.styles.filter(isValid))
|
|
292
292
|
for (const style of styles) {
|
|
293
293
|
try {
|
|
294
|
-
const globalStyleId = style.name as c4.GlobalStyleID
|
|
294
|
+
const globalStyleId = style.id.name as c4.GlobalStyleID
|
|
295
295
|
if (!isTruthy(globalStyleId)) {
|
|
296
296
|
continue
|
|
297
297
|
}
|
|
@@ -299,15 +299,30 @@ export class LikeC4ModelParser {
|
|
|
299
299
|
logger.warn(`Global style named "${globalStyleId}" is already defined`)
|
|
300
300
|
continue
|
|
301
301
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
302
|
+
|
|
303
|
+
const styles = this.parseGlobalStyleOrGroup(style, isValid)
|
|
304
|
+
if (styles.length > 0) {
|
|
305
|
+
c4Globals.styles[globalStyleId] = styles as c4.NonEmptyArray<c4.ViewRuleStyle>
|
|
306
|
+
}
|
|
305
307
|
} catch (e) {
|
|
306
308
|
logWarnError(e)
|
|
307
309
|
}
|
|
308
310
|
}
|
|
309
311
|
}
|
|
310
312
|
|
|
313
|
+
private parseGlobalStyleOrGroup(
|
|
314
|
+
astRule: ast.GlobalStyle | ast.GlobalStyleGroup,
|
|
315
|
+
isValid: IsValidFn
|
|
316
|
+
): c4.ViewRuleStyle[] {
|
|
317
|
+
if (ast.isGlobalStyle(astRule)) {
|
|
318
|
+
return [this.parseGlobalStyle(astRule, isValid)]
|
|
319
|
+
}
|
|
320
|
+
if (ast.isGlobalStyleGroup(astRule)) {
|
|
321
|
+
return astRule.styles.map(s => this.parseViewRuleStyle(s, isValid))
|
|
322
|
+
}
|
|
323
|
+
nonexhaustive(astRule)
|
|
324
|
+
}
|
|
325
|
+
|
|
311
326
|
private parseGlobalStyle(astRule: ast.GlobalStyle, isValid: IsValidFn): c4.ViewRuleStyle {
|
|
312
327
|
const styleProps = astRule.props.filter(ast.isStyleProperty)
|
|
313
328
|
const targets = astRule.target
|
|
@@ -319,7 +334,7 @@ export class LikeC4ModelParser {
|
|
|
319
334
|
const viewBlocks = doc.parseResult.value.views.filter(v => isValid(v))
|
|
320
335
|
for (const viewBlock of viewBlocks) {
|
|
321
336
|
const localStyles = viewBlock.styles
|
|
322
|
-
.flatMap(s => this.
|
|
337
|
+
.flatMap(s => this.parseViewRuleStyleOrGlobalRef(s, isValid))
|
|
323
338
|
const stylesToApply = localStyles
|
|
324
339
|
|
|
325
340
|
for (const view of viewBlock.views) {
|
|
@@ -530,8 +545,11 @@ export class LikeC4ModelParser {
|
|
|
530
545
|
|
|
531
546
|
private parseRelationPredicate(astNode: ast.RelationPredicate, _isValid: IsValidFn): c4.RelationPredicateExpression {
|
|
532
547
|
if (ast.isRelationPredicateWith(astNode)) {
|
|
533
|
-
|
|
534
|
-
|
|
548
|
+
let relation = ast.isRelationPredicateWhere(astNode.subject)
|
|
549
|
+
? this.parseRelationPredicateWhere(astNode.subject)
|
|
550
|
+
: this.parseRelationExpr(astNode.subject)
|
|
551
|
+
|
|
552
|
+
return this.parseRelationPredicateWith(astNode, relation)
|
|
535
553
|
}
|
|
536
554
|
if (ast.isRelationPredicateWhere(astNode)) {
|
|
537
555
|
return this.parseRelationPredicateWhere(astNode)
|
|
@@ -558,9 +576,8 @@ export class LikeC4ModelParser {
|
|
|
558
576
|
|
|
559
577
|
private parseRelationPredicateWith(
|
|
560
578
|
astNode: ast.RelationPredicateWith,
|
|
561
|
-
|
|
579
|
+
relation: c4.RelationExpression | c4.RelationWhereExpr
|
|
562
580
|
): c4.CustomRelationExpr {
|
|
563
|
-
const relation = this.parseRelationExpr(subject)
|
|
564
581
|
const props = astNode.custom?.props ?? []
|
|
565
582
|
return props.reduce(
|
|
566
583
|
(acc, prop) => {
|
|
@@ -636,12 +653,22 @@ export class LikeC4ModelParser {
|
|
|
636
653
|
if (ast.isViewRulePredicate(astRule)) {
|
|
637
654
|
return this.parseViewRulePredicate(astRule, isValid)
|
|
638
655
|
}
|
|
639
|
-
if (ast.
|
|
640
|
-
return this.
|
|
656
|
+
if (ast.isViewRuleStyleOrGlobalRef(astRule)) {
|
|
657
|
+
return this.parseViewRuleStyleOrGlobalRef(astRule, isValid)
|
|
641
658
|
}
|
|
642
659
|
if (ast.isViewRuleAutoLayout(astRule)) {
|
|
643
660
|
return toAutoLayout(astRule)
|
|
644
661
|
}
|
|
662
|
+
if (ast.isViewRuleGroup(astRule)) {
|
|
663
|
+
return this.parseViewRuleGroup(astRule, isValid)
|
|
664
|
+
}
|
|
665
|
+
nonexhaustive(astRule)
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
private parseViewRuleStyleOrGlobalRef(astRule: ast.ViewRuleStyleOrGlobalRef, isValid: IsValidFn): c4.ViewRuleStyleOrGlobalRef {
|
|
669
|
+
if (ast.isViewRuleStyle(astRule)) {
|
|
670
|
+
return this.parseViewRuleStyle(astRule, isValid)
|
|
671
|
+
}
|
|
645
672
|
if (ast.isViewRuleGlobalStyle(astRule)) {
|
|
646
673
|
return this.parseViewRuleGlobalStyle(astRule, isValid)
|
|
647
674
|
}
|
|
@@ -655,6 +682,33 @@ export class LikeC4ModelParser {
|
|
|
655
682
|
return this.parseRuleStyle(styleProps, targets, isValid, notation)
|
|
656
683
|
}
|
|
657
684
|
|
|
685
|
+
private parseViewRuleGroup(astNode: ast.ViewRuleGroup, _isValid: IsValidFn): c4.ViewRuleGroup {
|
|
686
|
+
const groupRules = [] as c4.ViewRuleGroup['groupRules']
|
|
687
|
+
for (const rule of astNode.groupRules) {
|
|
688
|
+
try {
|
|
689
|
+
if (!_isValid(rule)) {
|
|
690
|
+
continue
|
|
691
|
+
}
|
|
692
|
+
if (ast.isViewRulePredicate(rule)) {
|
|
693
|
+
groupRules.push(this.parseViewRulePredicate(rule, _isValid))
|
|
694
|
+
continue
|
|
695
|
+
}
|
|
696
|
+
if (ast.isViewRuleGroup(rule)) {
|
|
697
|
+
groupRules.push(this.parseViewRuleGroup(rule, _isValid))
|
|
698
|
+
continue
|
|
699
|
+
}
|
|
700
|
+
nonexhaustive(rule)
|
|
701
|
+
} catch (e) {
|
|
702
|
+
logWarnError(e)
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
return {
|
|
706
|
+
title: toSingleLine(astNode.title) ?? null,
|
|
707
|
+
groupRules,
|
|
708
|
+
...toElementStyle(astNode.props, _isValid)
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
658
712
|
private parseRuleStyle(
|
|
659
713
|
styleProperties: ast.StyleProperty[],
|
|
660
714
|
elementExpressionsIterator: ast.ElementExpressionsIterator,
|
|
@@ -673,7 +727,7 @@ export class LikeC4ModelParser {
|
|
|
673
727
|
}
|
|
674
728
|
}
|
|
675
729
|
|
|
676
|
-
private parseViewRuleGlobalStyle(astRule: ast.ViewRuleGlobalStyle,
|
|
730
|
+
private parseViewRuleGlobalStyle(astRule: ast.ViewRuleGlobalStyle, _isValid: IsValidFn): c4.ViewRuleGlobalStyle {
|
|
677
731
|
return {
|
|
678
732
|
styleId: astRule.style.$refText as c4.GlobalStyleID
|
|
679
733
|
}
|
|
@@ -777,7 +831,7 @@ export class LikeC4ModelParser {
|
|
|
777
831
|
|
|
778
832
|
private parseElementView(
|
|
779
833
|
astNode: ast.ElementView,
|
|
780
|
-
additionalStyles: c4.
|
|
834
|
+
additionalStyles: c4.ViewRuleStyleOrGlobalRef[],
|
|
781
835
|
isValid: IsValidFn
|
|
782
836
|
): ParsedAstElementView {
|
|
783
837
|
const body = astNode.body
|
|
@@ -849,7 +903,7 @@ export class LikeC4ModelParser {
|
|
|
849
903
|
|
|
850
904
|
private parseDynamicElementView(
|
|
851
905
|
astNode: ast.DynamicView,
|
|
852
|
-
additionalStyles: c4.
|
|
906
|
+
additionalStyles: c4.ViewRuleStyleOrGlobalRef[],
|
|
853
907
|
isValid: IsValidFn
|
|
854
908
|
): ParsedAstDynamicView {
|
|
855
909
|
const body = astNode.body
|
|
@@ -917,15 +971,12 @@ export class LikeC4ModelParser {
|
|
|
917
971
|
if (ast.isDynamicViewIncludePredicate(astRule)) {
|
|
918
972
|
return this.parseDynamicViewIncludePredicate(astRule, isValid)
|
|
919
973
|
}
|
|
920
|
-
if (ast.
|
|
921
|
-
return this.
|
|
974
|
+
if (ast.isViewRuleStyleOrGlobalRef(astRule)) {
|
|
975
|
+
return this.parseViewRuleStyleOrGlobalRef(astRule, isValid)
|
|
922
976
|
}
|
|
923
977
|
if (ast.isViewRuleAutoLayout(astRule)) {
|
|
924
978
|
return toAutoLayout(astRule)
|
|
925
979
|
}
|
|
926
|
-
if (ast.isViewRuleGlobalStyle(astRule)) {
|
|
927
|
-
return this.parseViewRuleGlobalStyle(astRule, isValid)
|
|
928
|
-
}
|
|
929
980
|
nonexhaustive(astRule)
|
|
930
981
|
}
|
|
931
982
|
|
|
@@ -11,8 +11,16 @@ import {
|
|
|
11
11
|
type ElementWhereExpr,
|
|
12
12
|
type Expression as C4Expression,
|
|
13
13
|
type Fqn,
|
|
14
|
+
type IncomingExpr as C4IncomingExpr,
|
|
15
|
+
type InOutExpr as C4InOutExpr,
|
|
16
|
+
isElementRef,
|
|
17
|
+
isElementWhere,
|
|
18
|
+
isRelationExpression,
|
|
19
|
+
isRelationWhere,
|
|
14
20
|
type NonEmptyArray,
|
|
21
|
+
type OutgoingExpr as C4OutgoingExpr,
|
|
15
22
|
type Relation,
|
|
23
|
+
type RelationExpr as C4RelationExpr,
|
|
16
24
|
type RelationID,
|
|
17
25
|
type RelationshipArrowType,
|
|
18
26
|
type RelationshipLineType,
|
|
@@ -20,6 +28,7 @@ import {
|
|
|
20
28
|
type Tag,
|
|
21
29
|
type ViewID,
|
|
22
30
|
type ViewRule,
|
|
31
|
+
type ViewRuleGroup,
|
|
23
32
|
type ViewRulePredicate,
|
|
24
33
|
type ViewRuleStyle,
|
|
25
34
|
type WhereOperator
|
|
@@ -362,8 +371,6 @@ export type Expression =
|
|
|
362
371
|
| IncomingExpr
|
|
363
372
|
| OutgoingExpr
|
|
364
373
|
| RelationExpr
|
|
365
|
-
| ElementWhereExpr
|
|
366
|
-
| RelationWhereExpr
|
|
367
374
|
|
|
368
375
|
export function $custom(
|
|
369
376
|
expr: ElementRefExpr,
|
|
@@ -411,6 +418,49 @@ export function $where(
|
|
|
411
418
|
}
|
|
412
419
|
}
|
|
413
420
|
|
|
421
|
+
export function $inout(
|
|
422
|
+
expr: InOutExpr | C4ElementExpression
|
|
423
|
+
): C4InOutExpr {
|
|
424
|
+
const innerExpression = !isString(expr)
|
|
425
|
+
? expr as C4Expression
|
|
426
|
+
: $expr(expr.replace(/->/g, '').trim() as ElementRefExpr) as any
|
|
427
|
+
|
|
428
|
+
return { inout: innerExpression }
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
export function $incoming(
|
|
432
|
+
expr: IncomingExpr | C4ElementExpression
|
|
433
|
+
): C4IncomingExpr {
|
|
434
|
+
const innerExpression = !isString(expr)
|
|
435
|
+
? expr as C4Expression
|
|
436
|
+
: $expr(expr.replace('-> ', '') as ElementRefExpr) as any
|
|
437
|
+
|
|
438
|
+
return { incoming: innerExpression }
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export function $outgoing(
|
|
442
|
+
expr: OutgoingExpr | C4ElementExpression
|
|
443
|
+
): C4OutgoingExpr {
|
|
444
|
+
const innerExpression = !isString(expr)
|
|
445
|
+
? expr as C4Expression
|
|
446
|
+
: $expr(expr.replace(' ->', '') as ElementRefExpr) as any
|
|
447
|
+
|
|
448
|
+
return { outgoing: innerExpression }
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
export function $relation(
|
|
452
|
+
expr: RelationExpr
|
|
453
|
+
): C4RelationExpr {
|
|
454
|
+
const [source, target] = expr.split(/ -> | <-> /)
|
|
455
|
+
const isBidirectional = expr.includes(' <-> ')
|
|
456
|
+
|
|
457
|
+
return {
|
|
458
|
+
source: $expr(source as ElementRefExpr) as any,
|
|
459
|
+
target: $expr(target as ElementRefExpr) as any,
|
|
460
|
+
...(isBidirectional && { isBidirectional })
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
414
464
|
export function $expr(expr: Expression | C4Expression): C4Expression {
|
|
415
465
|
if (!isString(expr)) {
|
|
416
466
|
return expr as C4Expression
|
|
@@ -419,34 +469,13 @@ export function $expr(expr: Expression | C4Expression): C4Expression {
|
|
|
419
469
|
return { wildcard: true }
|
|
420
470
|
}
|
|
421
471
|
if (expr.startsWith('->')) {
|
|
422
|
-
|
|
423
|
-
return {
|
|
424
|
-
inout: $expr(expr.replace(/->/g, '').trim() as ElementRefExpr) as any
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
return {
|
|
428
|
-
incoming: $expr(expr.replace('-> ', '') as ElementRefExpr) as any
|
|
429
|
-
}
|
|
472
|
+
return expr.endsWith('->') ? $inout(expr as InOutExpr) : $incoming(expr as IncomingExpr)
|
|
430
473
|
}
|
|
431
474
|
if (expr.endsWith(' ->')) {
|
|
432
|
-
return
|
|
433
|
-
outgoing: $expr(expr.replace(' ->', '') as ElementRefExpr) as any
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
if (expr.includes(' <-> ')) {
|
|
437
|
-
const [source, target] = expr.split(' <-> ')
|
|
438
|
-
return {
|
|
439
|
-
source: $expr(source as ElementRefExpr) as any,
|
|
440
|
-
target: $expr(target as ElementRefExpr) as any,
|
|
441
|
-
isBidirectional: true
|
|
442
|
-
}
|
|
475
|
+
return $outgoing(expr as OutgoingExpr)
|
|
443
476
|
}
|
|
444
|
-
if (expr.includes(' -> ')) {
|
|
445
|
-
|
|
446
|
-
return {
|
|
447
|
-
source: $expr(source as ElementRefExpr) as any,
|
|
448
|
-
target: $expr(target as ElementRefExpr) as any
|
|
449
|
-
}
|
|
477
|
+
if (expr.includes(' -> ') || expr.includes(' <-> ')) {
|
|
478
|
+
return $relation(expr as RelationExpr)
|
|
450
479
|
}
|
|
451
480
|
if (expr.endsWith('._')) {
|
|
452
481
|
return {
|
|
@@ -465,9 +494,41 @@ export function $expr(expr: Expression | C4Expression): C4Expression {
|
|
|
465
494
|
}
|
|
466
495
|
}
|
|
467
496
|
|
|
468
|
-
|
|
497
|
+
type CustomProps = {
|
|
498
|
+
where?: WhereOperator<TestTag, string>
|
|
499
|
+
with?: {
|
|
500
|
+
title?: string
|
|
501
|
+
description?: string
|
|
502
|
+
technology?: string
|
|
503
|
+
shape?: ElementShape
|
|
504
|
+
color?: Color
|
|
505
|
+
border?: BorderStyle
|
|
506
|
+
icon?: string
|
|
507
|
+
opacity?: number
|
|
508
|
+
navigateTo?: string
|
|
509
|
+
} & Omit<C4CustomRelationExpr['customRelation'], 'relation' | 'navigateTo'>
|
|
510
|
+
}
|
|
511
|
+
export function $include(expr: Expression | C4Expression, props?: CustomProps): ViewRulePredicate {
|
|
512
|
+
let _expr = props?.where ? $where(expr, props.where) : $expr(expr)
|
|
513
|
+
if (props?.with) {
|
|
514
|
+
if (isRelationExpression(_expr) || isRelationWhere(_expr)) {
|
|
515
|
+
_expr = {
|
|
516
|
+
customRelation: {
|
|
517
|
+
relation: _expr,
|
|
518
|
+
...props.with as any
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
} else if (isElementRef(_expr) || isElementWhere(_expr)) {
|
|
522
|
+
_expr = {
|
|
523
|
+
custom: {
|
|
524
|
+
expr: _expr,
|
|
525
|
+
...props.with as any
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
469
530
|
return {
|
|
470
|
-
include: [
|
|
531
|
+
include: [_expr]
|
|
471
532
|
}
|
|
472
533
|
}
|
|
473
534
|
export function $exclude(expr: Expression | C4Expression): ViewRulePredicate {
|
|
@@ -475,6 +536,12 @@ export function $exclude(expr: Expression | C4Expression): ViewRulePredicate {
|
|
|
475
536
|
exclude: [$expr(expr)]
|
|
476
537
|
}
|
|
477
538
|
}
|
|
539
|
+
export function $group(groupRules: ViewRuleGroup['groupRules']): ViewRuleGroup {
|
|
540
|
+
return {
|
|
541
|
+
title: null,
|
|
542
|
+
groupRules
|
|
543
|
+
}
|
|
544
|
+
}
|
|
478
545
|
|
|
479
546
|
export function $style(element: ElementRefExpr, style: ViewRuleStyle['style']): ViewRuleStyle {
|
|
480
547
|
return {
|