@likec4/language-server 1.13.0 → 1.15.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/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.80ITEDo5.cjs} +272 -64
- package/dist/shared/{language-server.C2ebP2pZ.cjs → language-server.BUtiWTKg.cjs} +383 -32
- package/dist/shared/{language-server.DFLaUdYu.mjs → language-server.DXC9g4_f.mjs} +274 -66
- package/dist/shared/{language-server.ryB8CivX.d.mts → language-server.DfMwkd2l.d.mts} +81 -15
- package/dist/shared/{language-server.Cnq_hgfm.d.cts → language-server.U2piOAVt.d.cts} +81 -15
- package/dist/shared/{language-server.eY70DuKx.d.ts → language-server.j-ShR6as.d.ts} +81 -15
- package/dist/shared/{language-server.CrE0nFSB.mjs → language-server.zY53FGJE.mjs} +385 -34
- package/package.json +12 -12
- package/src/ast.ts +11 -0
- package/src/generated/ast.ts +177 -17
- package/src/generated/grammar.ts +1 -1
- package/src/generated-lib/icons.ts +0 -1
- package/src/like-c4.langium +50 -4
- package/src/lsp/CompletionProvider.ts +70 -2
- package/src/model/model-builder.ts +25 -3
- package/src/model/model-parser.ts +150 -33
- package/src/model-graph/compute-view/__test__/fixture.ts +45 -4
- 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 +2 -1
- 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/sortNodes.ts +11 -7
- package/src/references/scope-computation.ts +24 -1
- package/src/validation/index.ts +4 -0
- package/src/validation/specification.ts +30 -0
- package/src/view-utils/index.ts +1 -0
- package/src/view-utils/resolve-global-rules.ts +72 -0
- package/dist/shared/language-server.B-9_mDoo.d.mts +0 -1238
- package/dist/shared/language-server.C3oS5yhF.d.cts +0 -1238
- package/dist/shared/language-server.r5AXAWzc.d.ts +0 -1238
package/src/like-c4.langium
CHANGED
|
@@ -5,6 +5,7 @@ entry LikeC4Grammar:
|
|
|
5
5
|
specifications+=SpecificationRule |
|
|
6
6
|
models+=Model |
|
|
7
7
|
views+=ModelViews |
|
|
8
|
+
globals+=Globals |
|
|
8
9
|
likec4lib+=LikeC4Lib
|
|
9
10
|
)*
|
|
10
11
|
;
|
|
@@ -196,7 +197,7 @@ MetadataAttribute:
|
|
|
196
197
|
ModelViews:
|
|
197
198
|
name='views' '{' (
|
|
198
199
|
views+=LikeC4ViewRule |
|
|
199
|
-
styles+=
|
|
200
|
+
styles+=ViewRuleStyleOrGlobalRef
|
|
200
201
|
)*
|
|
201
202
|
'}';
|
|
202
203
|
|
|
@@ -266,13 +267,27 @@ ViewLayoutDirection returns string:
|
|
|
266
267
|
|
|
267
268
|
ViewRule:
|
|
268
269
|
ViewRulePredicate |
|
|
269
|
-
|
|
270
|
+
ViewRuleGroup |
|
|
271
|
+
ViewRuleStyleOrGlobalRef |
|
|
270
272
|
ViewRuleAutoLayout
|
|
271
273
|
;
|
|
272
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
|
+
|
|
273
288
|
DynamicViewRule:
|
|
274
289
|
DynamicViewIncludePredicate |
|
|
275
|
-
|
|
290
|
+
ViewRuleStyleOrGlobalRef |
|
|
276
291
|
ViewRuleAutoLayout
|
|
277
292
|
;
|
|
278
293
|
|
|
@@ -440,6 +455,12 @@ ViewRuleStyle:
|
|
|
440
455
|
)*
|
|
441
456
|
'}';
|
|
442
457
|
|
|
458
|
+
ViewRuleGlobalStyle:
|
|
459
|
+
'global' 'style' style=[GlobalStyleId];
|
|
460
|
+
|
|
461
|
+
ViewRuleStyleOrGlobalRef:
|
|
462
|
+
ViewRuleStyle | ViewRuleGlobalStyle;
|
|
463
|
+
|
|
443
464
|
ViewRuleAutoLayout:
|
|
444
465
|
'autoLayout' direction=ViewLayoutDirection (
|
|
445
466
|
rankSep=Number (
|
|
@@ -480,6 +501,31 @@ NavigateToProperty:
|
|
|
480
501
|
RelationNavigateToProperty:
|
|
481
502
|
key='navigateTo' value=DynamicViewRef;
|
|
482
503
|
|
|
504
|
+
// Global -------------------------------------
|
|
505
|
+
|
|
506
|
+
Globals:
|
|
507
|
+
name='global' '{'
|
|
508
|
+
(
|
|
509
|
+
styles+=(GlobalStyle | GlobalStyleGroup)*
|
|
510
|
+
)*
|
|
511
|
+
'}';
|
|
512
|
+
|
|
513
|
+
GlobalStyleId:
|
|
514
|
+
name=IdTerminal;
|
|
515
|
+
|
|
516
|
+
GlobalStyle:
|
|
517
|
+
'style' id=GlobalStyleId target=ElementExpressionsIterator '{'
|
|
518
|
+
props+=(
|
|
519
|
+
StyleProperty |
|
|
520
|
+
NotationProperty
|
|
521
|
+
)*
|
|
522
|
+
'}';
|
|
523
|
+
|
|
524
|
+
GlobalStyleGroup:
|
|
525
|
+
'styleGroup' id=GlobalStyleId '{'
|
|
526
|
+
styles+=ViewRuleStyle*
|
|
527
|
+
'}';
|
|
528
|
+
|
|
483
529
|
// Common properties -------------------------------------
|
|
484
530
|
|
|
485
531
|
LinkProperty:
|
|
@@ -570,7 +616,7 @@ CustomColorId returns string:
|
|
|
570
616
|
IdTerminal | ElementShape | ArrowType | LineOptions | 'element' | 'model';
|
|
571
617
|
|
|
572
618
|
Id returns string:
|
|
573
|
-
IdTerminal | ElementShape | ThemeColor | ArrowType | LineOptions | 'element' | 'model';
|
|
619
|
+
IdTerminal | ElementShape | ThemeColor | ArrowType | LineOptions | 'element' | 'model' | 'group';
|
|
574
620
|
|
|
575
621
|
fragment EqOperator:
|
|
576
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),
|
|
@@ -13,6 +13,7 @@ import { deepEqual as eq } from 'fast-equals'
|
|
|
13
13
|
import type { Cancellation, LangiumDocument, LangiumDocuments, URI, WorkspaceCache } from 'langium'
|
|
14
14
|
import { Disposable, DocumentState, interruptAndCheck } from 'langium'
|
|
15
15
|
import {
|
|
16
|
+
entries,
|
|
16
17
|
filter,
|
|
17
18
|
flatMap,
|
|
18
19
|
forEach,
|
|
@@ -45,8 +46,7 @@ import { isParsedLikeC4LangiumDocument } from '../ast'
|
|
|
45
46
|
import { logError, logger, logWarnError } from '../logger'
|
|
46
47
|
import { computeDynamicView, computeView, LikeC4ModelGraph } from '../model-graph'
|
|
47
48
|
import type { LikeC4Services } from '../module'
|
|
48
|
-
import {
|
|
49
|
-
import { assignNavigateTo, resolveRelativePaths, resolveRulesExtendedViews } from '../view-utils'
|
|
49
|
+
import { assignNavigateTo, resolveGlobalRules, resolveRelativePaths, resolveRulesExtendedViews } from '../view-utils'
|
|
50
50
|
|
|
51
51
|
function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[]): c4.ParsedLikeC4Model {
|
|
52
52
|
const c4Specification: ParsedAstSpecification = {
|
|
@@ -211,6 +211,23 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
211
211
|
indexBy(prop('id'))
|
|
212
212
|
)
|
|
213
213
|
|
|
214
|
+
const parsedGlobals: {
|
|
215
|
+
styles: Record<c4.GlobalStyleID, c4.ViewRuleStyle[]>
|
|
216
|
+
} = {
|
|
217
|
+
styles: {}
|
|
218
|
+
}
|
|
219
|
+
Object.assign(parsedGlobals.styles, ...docs.map(d => d.c4Globals.styles))
|
|
220
|
+
|
|
221
|
+
const globals: {
|
|
222
|
+
styles: Record<c4.GlobalStyleID, c4.GlobalStyle>
|
|
223
|
+
} = {
|
|
224
|
+
styles: pipe(
|
|
225
|
+
entries(parsedGlobals.styles),
|
|
226
|
+
map(([id, styles]) => ({ id, styles })),
|
|
227
|
+
indexBy(prop('id'))
|
|
228
|
+
)
|
|
229
|
+
}
|
|
230
|
+
|
|
214
231
|
function toC4View(doc: LangiumDocument) {
|
|
215
232
|
const docUri = doc.uri.toString()
|
|
216
233
|
return (parsedAstView: ParsedAstView): c4.LikeC4View => {
|
|
@@ -287,6 +304,7 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
287
304
|
},
|
|
288
305
|
elements,
|
|
289
306
|
relations,
|
|
307
|
+
globals,
|
|
290
308
|
views
|
|
291
309
|
}
|
|
292
310
|
}
|
|
@@ -396,6 +414,7 @@ export class LikeC4ModelBuilder {
|
|
|
396
414
|
specification: model.specification,
|
|
397
415
|
elements: model.elements,
|
|
398
416
|
relations: model.relations,
|
|
417
|
+
globals: model.globals,
|
|
399
418
|
views
|
|
400
419
|
}
|
|
401
420
|
})
|
|
@@ -441,7 +460,10 @@ export class LikeC4ModelBuilder {
|
|
|
441
460
|
return null
|
|
442
461
|
}
|
|
443
462
|
const index = new LikeC4ModelGraph(model)
|
|
444
|
-
const
|
|
463
|
+
const resolvedView = resolveGlobalRules(view, model.globals.styles)
|
|
464
|
+
const result = isElementView(resolvedView)
|
|
465
|
+
? computeView(resolvedView, index)
|
|
466
|
+
: computeDynamicView(resolvedView, index)
|
|
445
467
|
if (!result.isSuccess) {
|
|
446
468
|
logError(result.error)
|
|
447
469
|
return null
|
|
@@ -30,15 +30,13 @@ import {
|
|
|
30
30
|
ViewOps
|
|
31
31
|
} from '../ast'
|
|
32
32
|
import { elementRef, getFqnElementRef } from '../elementRef'
|
|
33
|
+
import { isGlobalStyle, isGlobalStyleGroup, type NotationProperty } from '../generated/ast'
|
|
33
34
|
import { logError, logger, logWarnError } from '../logger'
|
|
34
35
|
import type { LikeC4Services } from '../module'
|
|
35
36
|
import { stringHash } from '../utils'
|
|
36
37
|
import { deserializeFromComment, hasManualLayout } from '../view-utils/manual-layout'
|
|
37
38
|
import type { FqnIndex } from './fqn-index'
|
|
38
39
|
import { parseWhereClause } from './model-parser-where'
|
|
39
|
-
import {
|
|
40
|
-
type NotationProperty
|
|
41
|
-
} from '../generated/ast'
|
|
42
40
|
|
|
43
41
|
const { getDocument } = AstUtils
|
|
44
42
|
|
|
@@ -83,6 +81,7 @@ export class LikeC4ModelParser {
|
|
|
83
81
|
const { isValid } = checksFromDiagnostics(doc)
|
|
84
82
|
this.parseSpecification(doc, isValid)
|
|
85
83
|
this.parseModel(doc, isValid)
|
|
84
|
+
this.parseGlobal(doc, isValid)
|
|
86
85
|
this.parseViews(doc, isValid)
|
|
87
86
|
return doc
|
|
88
87
|
}
|
|
@@ -285,11 +284,57 @@ export class LikeC4ModelParser {
|
|
|
285
284
|
}
|
|
286
285
|
}
|
|
287
286
|
|
|
287
|
+
private parseGlobal(doc: ParsedLikeC4LangiumDocument, isValid: IsValidFn) {
|
|
288
|
+
const { parseResult, c4Globals } = doc
|
|
289
|
+
|
|
290
|
+
const globals = parseResult.value.globals.filter(isValid)
|
|
291
|
+
const styles = globals.flatMap(r => r.styles.filter(isValid))
|
|
292
|
+
for (const style of styles) {
|
|
293
|
+
try {
|
|
294
|
+
const globalStyleId = style.id.name as c4.GlobalStyleID
|
|
295
|
+
if (!isTruthy(globalStyleId)) {
|
|
296
|
+
continue
|
|
297
|
+
}
|
|
298
|
+
if (globalStyleId in c4Globals.styles) {
|
|
299
|
+
logger.warn(`Global style named "${globalStyleId}" is already defined`)
|
|
300
|
+
continue
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const styles = this.parseGlobalStyleOrGroup(style, isValid)
|
|
304
|
+
if (styles.length > 0) {
|
|
305
|
+
c4Globals.styles[globalStyleId] = styles as c4.NonEmptyArray<c4.ViewRuleStyle>
|
|
306
|
+
}
|
|
307
|
+
} catch (e) {
|
|
308
|
+
logWarnError(e)
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
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
|
+
|
|
326
|
+
private parseGlobalStyle(astRule: ast.GlobalStyle, isValid: IsValidFn): c4.ViewRuleStyle {
|
|
327
|
+
const styleProps = astRule.props.filter(ast.isStyleProperty)
|
|
328
|
+
const targets = astRule.target
|
|
329
|
+
const notation = astRule.props.find(ast.isNotationProperty)
|
|
330
|
+
return this.parseRuleStyle(styleProps, targets, isValid, notation)
|
|
331
|
+
}
|
|
332
|
+
|
|
288
333
|
private parseViews(doc: ParsedLikeC4LangiumDocument, isValid: IsValidFn) {
|
|
289
334
|
const viewBlocks = doc.parseResult.value.views.filter(v => isValid(v))
|
|
290
335
|
for (const viewBlock of viewBlocks) {
|
|
291
336
|
const localStyles = viewBlock.styles
|
|
292
|
-
.flatMap(s => this.
|
|
337
|
+
.flatMap(s => this.parseViewRuleStyleOrGlobalRef(s, isValid))
|
|
293
338
|
const stylesToApply = localStyles
|
|
294
339
|
|
|
295
340
|
for (const view of viewBlock.views) {
|
|
@@ -298,7 +343,9 @@ export class LikeC4ModelParser {
|
|
|
298
343
|
continue
|
|
299
344
|
}
|
|
300
345
|
doc.c4Views.push(
|
|
301
|
-
ast.isElementView(view)
|
|
346
|
+
ast.isElementView(view)
|
|
347
|
+
? this.parseElementView(view, stylesToApply, isValid)
|
|
348
|
+
: this.parseDynamicElementView(view, stylesToApply, isValid)
|
|
302
349
|
)
|
|
303
350
|
} catch (e) {
|
|
304
351
|
logWarnError(e)
|
|
@@ -498,8 +545,11 @@ export class LikeC4ModelParser {
|
|
|
498
545
|
|
|
499
546
|
private parseRelationPredicate(astNode: ast.RelationPredicate, _isValid: IsValidFn): c4.RelationPredicateExpression {
|
|
500
547
|
if (ast.isRelationPredicateWith(astNode)) {
|
|
501
|
-
|
|
502
|
-
|
|
548
|
+
let relation = ast.isRelationPredicateWhere(astNode.subject)
|
|
549
|
+
? this.parseRelationPredicateWhere(astNode.subject)
|
|
550
|
+
: this.parseRelationExpr(astNode.subject)
|
|
551
|
+
|
|
552
|
+
return this.parseRelationPredicateWith(astNode, relation)
|
|
503
553
|
}
|
|
504
554
|
if (ast.isRelationPredicateWhere(astNode)) {
|
|
505
555
|
return this.parseRelationPredicateWhere(astNode)
|
|
@@ -526,9 +576,8 @@ export class LikeC4ModelParser {
|
|
|
526
576
|
|
|
527
577
|
private parseRelationPredicateWith(
|
|
528
578
|
astNode: ast.RelationPredicateWith,
|
|
529
|
-
|
|
579
|
+
relation: c4.RelationExpression | c4.RelationWhereExpr
|
|
530
580
|
): c4.CustomRelationExpr {
|
|
531
|
-
const relation = this.parseRelationExpr(subject)
|
|
532
581
|
const props = astNode.custom?.props ?? []
|
|
533
582
|
return props.reduce(
|
|
534
583
|
(acc, prop) => {
|
|
@@ -604,12 +653,25 @@ export class LikeC4ModelParser {
|
|
|
604
653
|
if (ast.isViewRulePredicate(astRule)) {
|
|
605
654
|
return this.parseViewRulePredicate(astRule, isValid)
|
|
606
655
|
}
|
|
607
|
-
if (ast.
|
|
608
|
-
return this.
|
|
656
|
+
if (ast.isViewRuleStyleOrGlobalRef(astRule)) {
|
|
657
|
+
return this.parseViewRuleStyleOrGlobalRef(astRule, isValid)
|
|
609
658
|
}
|
|
610
659
|
if (ast.isViewRuleAutoLayout(astRule)) {
|
|
611
660
|
return toAutoLayout(astRule)
|
|
612
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
|
+
}
|
|
672
|
+
if (ast.isViewRuleGlobalStyle(astRule)) {
|
|
673
|
+
return this.parseViewRuleGlobalStyle(astRule, isValid)
|
|
674
|
+
}
|
|
613
675
|
nonexhaustive(astRule)
|
|
614
676
|
}
|
|
615
677
|
|
|
@@ -620,7 +682,39 @@ export class LikeC4ModelParser {
|
|
|
620
682
|
return this.parseRuleStyle(styleProps, targets, isValid, notation)
|
|
621
683
|
}
|
|
622
684
|
|
|
623
|
-
private
|
|
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
|
+
|
|
712
|
+
private parseRuleStyle(
|
|
713
|
+
styleProperties: ast.StyleProperty[],
|
|
714
|
+
elementExpressionsIterator: ast.ElementExpressionsIterator,
|
|
715
|
+
isValid: IsValidFn,
|
|
716
|
+
notationProperty?: NotationProperty
|
|
717
|
+
): c4.ViewRuleStyle {
|
|
624
718
|
const styleProps = toElementStyle(styleProperties, isValid)
|
|
625
719
|
const notation = removeIndent(notationProperty?.value)
|
|
626
720
|
const targets = this.parseElementExpressionsIterator(elementExpressionsIterator)
|
|
@@ -633,6 +727,12 @@ export class LikeC4ModelParser {
|
|
|
633
727
|
}
|
|
634
728
|
}
|
|
635
729
|
|
|
730
|
+
private parseViewRuleGlobalStyle(astRule: ast.ViewRuleGlobalStyle, _isValid: IsValidFn): c4.ViewRuleGlobalStyle {
|
|
731
|
+
return {
|
|
732
|
+
styleId: astRule.style.$refText as c4.GlobalStyleID
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
636
736
|
private parseViewManualLaout(node: ast.DynamicView | ast.ElementView): c4.ViewManualLayout | undefined {
|
|
637
737
|
const commentNode = CstUtils.findCommentNode(node.$cstNode, ['BLOCK_COMMENT'])
|
|
638
738
|
if (!commentNode || !hasManualLayout(commentNode.text)) {
|
|
@@ -729,7 +829,11 @@ export class LikeC4ModelParser {
|
|
|
729
829
|
return step
|
|
730
830
|
}
|
|
731
831
|
|
|
732
|
-
private parseElementView(
|
|
832
|
+
private parseElementView(
|
|
833
|
+
astNode: ast.ElementView,
|
|
834
|
+
additionalStyles: c4.ViewRuleStyleOrGlobalRef[],
|
|
835
|
+
isValid: IsValidFn
|
|
836
|
+
): ParsedAstElementView {
|
|
733
837
|
const body = astNode.body
|
|
734
838
|
invariant(body, 'ElementView body is not defined')
|
|
735
839
|
const astPath = this.getAstNodePath(astNode)
|
|
@@ -770,14 +874,17 @@ export class LikeC4ModelParser {
|
|
|
770
874
|
description,
|
|
771
875
|
tags,
|
|
772
876
|
links: isNonEmptyArray(links) ? links : null,
|
|
773
|
-
rules: [
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
877
|
+
rules: [
|
|
878
|
+
...additionalStyles,
|
|
879
|
+
...body.rules.flatMap(n => {
|
|
880
|
+
try {
|
|
881
|
+
return isValid(n) ? this.parseViewRule(n, isValid) : []
|
|
882
|
+
} catch (e) {
|
|
883
|
+
logWarnError(e)
|
|
884
|
+
return []
|
|
885
|
+
}
|
|
886
|
+
})
|
|
887
|
+
],
|
|
781
888
|
...(viewOf && { viewOf }),
|
|
782
889
|
...(manualLayout && { manualLayout })
|
|
783
890
|
}
|
|
@@ -794,7 +901,11 @@ export class LikeC4ModelParser {
|
|
|
794
901
|
return view
|
|
795
902
|
}
|
|
796
903
|
|
|
797
|
-
private parseDynamicElementView(
|
|
904
|
+
private parseDynamicElementView(
|
|
905
|
+
astNode: ast.DynamicView,
|
|
906
|
+
additionalStyles: c4.ViewRuleStyleOrGlobalRef[],
|
|
907
|
+
isValid: IsValidFn
|
|
908
|
+
): ParsedAstDynamicView {
|
|
798
909
|
const body = astNode.body
|
|
799
910
|
invariant(body, 'DynamicElementView body is not defined')
|
|
800
911
|
// only valid props
|
|
@@ -827,14 +938,17 @@ export class LikeC4ModelParser {
|
|
|
827
938
|
description,
|
|
828
939
|
tags,
|
|
829
940
|
links: isNonEmptyArray(links) ? links : null,
|
|
830
|
-
rules: [
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
941
|
+
rules: [
|
|
942
|
+
...additionalStyles,
|
|
943
|
+
...body.rules.flatMap(n => {
|
|
944
|
+
try {
|
|
945
|
+
return isValid(n) ? this.parseDynamicViewRule(n, isValid) : []
|
|
946
|
+
} catch (e) {
|
|
947
|
+
logWarnError(e)
|
|
948
|
+
return []
|
|
949
|
+
}
|
|
950
|
+
}, [] as Array<c4.DynamicViewRule>)
|
|
951
|
+
],
|
|
838
952
|
steps: body.steps.reduce((acc, n) => {
|
|
839
953
|
try {
|
|
840
954
|
if (isValid(n)) {
|
|
@@ -857,8 +971,8 @@ export class LikeC4ModelParser {
|
|
|
857
971
|
if (ast.isDynamicViewIncludePredicate(astRule)) {
|
|
858
972
|
return this.parseDynamicViewIncludePredicate(astRule, isValid)
|
|
859
973
|
}
|
|
860
|
-
if (ast.
|
|
861
|
-
return this.
|
|
974
|
+
if (ast.isViewRuleStyleOrGlobalRef(astRule)) {
|
|
975
|
+
return this.parseViewRuleStyleOrGlobalRef(astRule, isValid)
|
|
862
976
|
}
|
|
863
977
|
if (ast.isViewRuleAutoLayout(astRule)) {
|
|
864
978
|
return toAutoLayout(astRule)
|
|
@@ -866,7 +980,10 @@ export class LikeC4ModelParser {
|
|
|
866
980
|
nonexhaustive(astRule)
|
|
867
981
|
}
|
|
868
982
|
|
|
869
|
-
private parseDynamicViewIncludePredicate(
|
|
983
|
+
private parseDynamicViewIncludePredicate(
|
|
984
|
+
astRule: ast.DynamicViewIncludePredicate,
|
|
985
|
+
isValid: IsValidFn
|
|
986
|
+
): c4.DynamicViewIncludeRule {
|
|
870
987
|
const include = [] as c4.ElementPredicateExpression[]
|
|
871
988
|
let iter: ast.DynamicViewPredicateIterator | undefined = astRule.predicates
|
|
872
989
|
while (iter) {
|
|
@@ -11,6 +11,10 @@ import {
|
|
|
11
11
|
type ElementWhereExpr,
|
|
12
12
|
type Expression as C4Expression,
|
|
13
13
|
type Fqn,
|
|
14
|
+
isElementRef,
|
|
15
|
+
isElementWhere,
|
|
16
|
+
isRelationExpression,
|
|
17
|
+
isRelationWhere,
|
|
14
18
|
type NonEmptyArray,
|
|
15
19
|
type Relation,
|
|
16
20
|
type RelationID,
|
|
@@ -20,6 +24,7 @@ import {
|
|
|
20
24
|
type Tag,
|
|
21
25
|
type ViewID,
|
|
22
26
|
type ViewRule,
|
|
27
|
+
type ViewRuleGroup,
|
|
23
28
|
type ViewRulePredicate,
|
|
24
29
|
type ViewRuleStyle,
|
|
25
30
|
type WhereOperator
|
|
@@ -362,8 +367,6 @@ export type Expression =
|
|
|
362
367
|
| IncomingExpr
|
|
363
368
|
| OutgoingExpr
|
|
364
369
|
| RelationExpr
|
|
365
|
-
| ElementWhereExpr
|
|
366
|
-
| RelationWhereExpr
|
|
367
370
|
|
|
368
371
|
export function $custom(
|
|
369
372
|
expr: ElementRefExpr,
|
|
@@ -465,9 +468,41 @@ export function $expr(expr: Expression | C4Expression): C4Expression {
|
|
|
465
468
|
}
|
|
466
469
|
}
|
|
467
470
|
|
|
468
|
-
|
|
471
|
+
type CustomProps = {
|
|
472
|
+
where?: WhereOperator<TestTag, string>
|
|
473
|
+
with?: {
|
|
474
|
+
title?: string
|
|
475
|
+
description?: string
|
|
476
|
+
technology?: string
|
|
477
|
+
shape?: ElementShape
|
|
478
|
+
color?: Color
|
|
479
|
+
border?: BorderStyle
|
|
480
|
+
icon?: string
|
|
481
|
+
opacity?: number
|
|
482
|
+
navigateTo?: string
|
|
483
|
+
} & Omit<C4CustomRelationExpr['customRelation'], 'relation' | 'navigateTo'>
|
|
484
|
+
}
|
|
485
|
+
export function $include(expr: Expression | C4Expression, props?: CustomProps): ViewRulePredicate {
|
|
486
|
+
let _expr = props?.where ? $where(expr, props.where) : $expr(expr)
|
|
487
|
+
if (props?.with) {
|
|
488
|
+
if (isRelationExpression(_expr) || isRelationWhere(_expr)) {
|
|
489
|
+
_expr = {
|
|
490
|
+
customRelation: {
|
|
491
|
+
relation: _expr,
|
|
492
|
+
...props.with as any
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
} else if (isElementRef(_expr) || isElementWhere(_expr)) {
|
|
496
|
+
_expr = {
|
|
497
|
+
custom: {
|
|
498
|
+
expr: _expr,
|
|
499
|
+
...props.with as any
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
469
504
|
return {
|
|
470
|
-
include: [
|
|
505
|
+
include: [_expr]
|
|
471
506
|
}
|
|
472
507
|
}
|
|
473
508
|
export function $exclude(expr: Expression | C4Expression): ViewRulePredicate {
|
|
@@ -475,6 +510,12 @@ export function $exclude(expr: Expression | C4Expression): ViewRulePredicate {
|
|
|
475
510
|
exclude: [$expr(expr)]
|
|
476
511
|
}
|
|
477
512
|
}
|
|
513
|
+
export function $group(groupRules: ViewRuleGroup['groupRules']): ViewRuleGroup {
|
|
514
|
+
return {
|
|
515
|
+
title: null,
|
|
516
|
+
groupRules
|
|
517
|
+
}
|
|
518
|
+
}
|
|
478
519
|
|
|
479
520
|
export function $style(element: ElementRefExpr, style: ViewRuleStyle['style']): ViewRuleStyle {
|
|
480
521
|
return {
|