@likec4/language-server 1.15.1 → 1.17.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 +35 -5
- package/dist/index.d.cts +17 -4
- package/dist/index.d.mts +17 -4
- package/dist/index.d.ts +17 -4
- package/dist/index.mjs +34 -5
- package/dist/model-graph/index.cjs +1 -1
- package/dist/model-graph/index.d.cts +4 -2
- package/dist/model-graph/index.d.mts +4 -2
- package/dist/model-graph/index.d.ts +4 -2
- package/dist/model-graph/index.mjs +1 -1
- package/dist/shared/{language-server.BuChFlda.mjs → language-server.B8qSDsWW.mjs} +211 -96
- package/dist/shared/language-server.BGGRJRnr.d.mts +1338 -0
- package/dist/shared/{language-server.BT4WTbFI.mjs → language-server.BXFhlTPo.mjs} +139 -76
- package/dist/shared/{language-server.DfMwkd2l.d.mts → language-server.BgDKnNok.d.mts} +45 -11
- package/dist/shared/language-server.Bmpq16Gw.d.ts +1338 -0
- package/dist/shared/language-server.C1ZfM22X.d.cts +1338 -0
- package/dist/shared/{language-server.U2piOAVt.d.cts → language-server.DJo88TnT.d.cts} +45 -11
- package/dist/shared/{language-server.DfjkvknB.cjs → language-server.DZRuJVSg.cjs} +209 -94
- package/dist/shared/{language-server.B8s2wfT_.cjs → language-server.N8HLDQqz.cjs} +137 -74
- package/dist/shared/{language-server.j-ShR6as.d.ts → language-server.PEjk7U9s.d.ts} +45 -11
- package/package.json +7 -7
- package/src/LikeC4FileSystem.ts +36 -0
- package/src/Rpc.ts +2 -2
- package/src/ast.ts +11 -3
- package/src/generated/ast.ts +112 -11
- package/src/generated/grammar.ts +1 -1
- package/src/index.ts +3 -3
- package/src/like-c4.langium +20 -1
- package/src/lsp/SemanticTokenProvider.ts +26 -8
- package/src/model/fqn-computation.ts +6 -2
- package/src/model/model-builder.ts +25 -30
- package/src/model/model-parser.ts +91 -18
- package/src/model-graph/LikeC4ModelGraph.ts +22 -11
- package/src/model-graph/compute-view/__test__/fixture.ts +69 -19
- package/src/model-graph/compute-view/compute.ts +85 -73
- package/src/model-graph/dynamic-view/compute.ts +12 -2
- package/src/model-graph/utils/applyCustomElementProperties.ts +1 -3
- package/src/model-graph/utils/applyViewRuleStyles.ts +0 -4
- package/src/references/scope-computation.ts +10 -0
- package/src/validation/index.ts +3 -0
- package/src/validation/specification.ts +21 -0
- package/src/view-utils/index.ts +0 -1
- package/src/view-utils/resolve-global-rules.ts +66 -50
package/src/index.ts
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { startLanguageServer as startLanguim } from 'langium/lsp'
|
|
2
|
-
import { NodeFileSystem } from 'langium/node'
|
|
3
2
|
import { createConnection, ProposedFeatures } from 'vscode-languageserver/node'
|
|
3
|
+
import { LikeC4FileSystem } from './LikeC4FileSystem'
|
|
4
4
|
import { createLanguageServices } from './module'
|
|
5
5
|
|
|
6
6
|
export { logger as lspLogger, setLogLevel } from './logger'
|
|
7
7
|
export type * from './model'
|
|
8
8
|
export type * from './module'
|
|
9
9
|
export { createCustomLanguageServices, createLanguageServices, LikeC4Module } from './module'
|
|
10
|
-
|
|
10
|
+
export { LikeC4FileSystem }
|
|
11
11
|
export function startLanguageServer() {
|
|
12
12
|
/* browser specific setup code */
|
|
13
13
|
const connection = createConnection(ProposedFeatures.all)
|
|
14
14
|
|
|
15
15
|
// Inject the shared services and language-specific services
|
|
16
|
-
const services = createLanguageServices({ connection, ...
|
|
16
|
+
const services = createLanguageServices({ connection, ...LikeC4FileSystem })
|
|
17
17
|
|
|
18
18
|
// Start the language server with the shared services
|
|
19
19
|
startLanguim(services.shared)
|
package/src/like-c4.langium
CHANGED
|
@@ -267,6 +267,7 @@ ViewLayoutDirection returns string:
|
|
|
267
267
|
|
|
268
268
|
ViewRule:
|
|
269
269
|
ViewRulePredicate |
|
|
270
|
+
ViewRuleGlobalPredicateRef |
|
|
270
271
|
ViewRuleGroup |
|
|
271
272
|
ViewRuleStyleOrGlobalRef |
|
|
272
273
|
ViewRuleAutoLayout
|
|
@@ -287,6 +288,7 @@ ViewRuleGroup:
|
|
|
287
288
|
|
|
288
289
|
DynamicViewRule:
|
|
289
290
|
DynamicViewIncludePredicate |
|
|
291
|
+
DynamicViewGlobalPredicateRef |
|
|
290
292
|
ViewRuleStyleOrGlobalRef |
|
|
291
293
|
ViewRuleAutoLayout
|
|
292
294
|
;
|
|
@@ -318,6 +320,9 @@ Predicate:
|
|
|
318
320
|
ElementPredicate
|
|
319
321
|
;
|
|
320
322
|
|
|
323
|
+
ViewRuleGlobalPredicateRef:
|
|
324
|
+
'global' 'predicate' predicate=[GlobalPredicateGroup];
|
|
325
|
+
|
|
321
326
|
ElementPredicate:
|
|
322
327
|
ElementPredicates;
|
|
323
328
|
|
|
@@ -443,6 +448,9 @@ DynamicViewIncludePredicate:
|
|
|
443
448
|
'include' predicates=DynamicViewPredicateIterator
|
|
444
449
|
;
|
|
445
450
|
|
|
451
|
+
DynamicViewGlobalPredicateRef:
|
|
452
|
+
'global' 'predicate' predicate=[GlobalDynamicPredicateGroup];
|
|
453
|
+
|
|
446
454
|
DynamicViewPredicateIterator:
|
|
447
455
|
value=ElementPredicate ({infer DynamicViewPredicateIterator.prev=current} ',' (value=ElementPredicate)?)*
|
|
448
456
|
;
|
|
@@ -506,10 +514,21 @@ RelationNavigateToProperty:
|
|
|
506
514
|
Globals:
|
|
507
515
|
name='global' '{'
|
|
508
516
|
(
|
|
517
|
+
predicates+=(GlobalPredicateGroup | GlobalDynamicPredicateGroup)*
|
|
509
518
|
styles+=(GlobalStyle | GlobalStyleGroup)*
|
|
510
519
|
)*
|
|
511
520
|
'}';
|
|
512
521
|
|
|
522
|
+
GlobalPredicateGroup:
|
|
523
|
+
'predicateGroup' name=IdTerminal '{'
|
|
524
|
+
predicates+=ViewRulePredicate*
|
|
525
|
+
'}';
|
|
526
|
+
|
|
527
|
+
GlobalDynamicPredicateGroup:
|
|
528
|
+
'dynamicPredicateGroup' name=IdTerminal '{'
|
|
529
|
+
predicates+=DynamicViewIncludePredicate*
|
|
530
|
+
'}';
|
|
531
|
+
|
|
513
532
|
GlobalStyleId:
|
|
514
533
|
name=IdTerminal;
|
|
515
534
|
|
|
@@ -538,7 +557,7 @@ OpacityProperty:
|
|
|
538
557
|
|
|
539
558
|
// Element properties -------------------------------------
|
|
540
559
|
IconProperty:
|
|
541
|
-
key='icon' ':'? (libicon=[LibIcon:IconId] | value=Uri) ';'?;
|
|
560
|
+
key='icon' ':'? (libicon=[LibIcon:IconId] | value=('none'|Uri)) ';'?;
|
|
542
561
|
|
|
543
562
|
ShapeProperty:
|
|
544
563
|
key='shape' ':'? value=ElementShape ';'?;
|
|
@@ -196,6 +196,32 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
|
|
|
196
196
|
property: 'key',
|
|
197
197
|
type: SemanticTokenTypes.property
|
|
198
198
|
})
|
|
199
|
+
if (ast.isIconProperty(node)) {
|
|
200
|
+
if (node.libicon) {
|
|
201
|
+
acceptor({
|
|
202
|
+
node,
|
|
203
|
+
property: 'libicon',
|
|
204
|
+
type: SemanticTokenTypes.enum,
|
|
205
|
+
modifier: [SemanticTokenModifiers.defaultLibrary]
|
|
206
|
+
})
|
|
207
|
+
} else {
|
|
208
|
+
if (node.value === 'none') {
|
|
209
|
+
acceptor({
|
|
210
|
+
node,
|
|
211
|
+
property: 'value',
|
|
212
|
+
type: SemanticTokenTypes.enum,
|
|
213
|
+
modifier: [SemanticTokenModifiers.defaultLibrary]
|
|
214
|
+
})
|
|
215
|
+
} else {
|
|
216
|
+
acceptor({
|
|
217
|
+
node,
|
|
218
|
+
property: 'value',
|
|
219
|
+
type: SemanticTokenTypes.string
|
|
220
|
+
})
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return 'prune'
|
|
224
|
+
}
|
|
199
225
|
if ('value' in node && node.value) {
|
|
200
226
|
acceptor({
|
|
201
227
|
node,
|
|
@@ -203,14 +229,6 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
|
|
|
203
229
|
type: SemanticTokenTypes.string
|
|
204
230
|
})
|
|
205
231
|
}
|
|
206
|
-
if (ast.isIconProperty(node) && node.libicon) {
|
|
207
|
-
acceptor({
|
|
208
|
-
node,
|
|
209
|
-
property: 'libicon',
|
|
210
|
-
type: SemanticTokenTypes.enum,
|
|
211
|
-
modifier: [SemanticTokenModifiers.defaultLibrary]
|
|
212
|
-
})
|
|
213
|
-
}
|
|
214
232
|
return 'prune'
|
|
215
233
|
}
|
|
216
234
|
if (
|
|
@@ -52,7 +52,9 @@ export function computeDocumentFqn(document: LikeC4LangiumDocument, services: Li
|
|
|
52
52
|
if (isDefined(el.body) && !isEmpty(el.body.elements)) {
|
|
53
53
|
const fqn = getFqnElementRef(el.element)
|
|
54
54
|
for (const child of el.body.elements) {
|
|
55
|
-
|
|
55
|
+
if (!ast.isRelation(child)) {
|
|
56
|
+
traverseStack.push([child, fqn])
|
|
57
|
+
}
|
|
56
58
|
}
|
|
57
59
|
}
|
|
58
60
|
continue
|
|
@@ -66,7 +68,9 @@ export function computeDocumentFqn(document: LikeC4LangiumDocument, services: Li
|
|
|
66
68
|
ElementOps.writeId(el, fqn)
|
|
67
69
|
if (isDefined(el.body) && !isEmpty(el.body.elements)) {
|
|
68
70
|
for (const child of el.body.elements) {
|
|
69
|
-
|
|
71
|
+
if (!ast.isRelation(child)) {
|
|
72
|
+
traverseStack.push([child, fqn])
|
|
73
|
+
}
|
|
70
74
|
}
|
|
71
75
|
}
|
|
72
76
|
continue
|
|
@@ -13,10 +13,8 @@ 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,
|
|
17
16
|
filter,
|
|
18
17
|
flatMap,
|
|
19
|
-
forEach,
|
|
20
18
|
groupBy,
|
|
21
19
|
indexBy,
|
|
22
20
|
isEmpty,
|
|
@@ -46,21 +44,37 @@ import { isParsedLikeC4LangiumDocument } from '../ast'
|
|
|
46
44
|
import { logError, logger, logWarnError } from '../logger'
|
|
47
45
|
import { computeDynamicView, computeView, LikeC4ModelGraph } from '../model-graph'
|
|
48
46
|
import type { LikeC4Services } from '../module'
|
|
49
|
-
import { assignNavigateTo,
|
|
47
|
+
import { assignNavigateTo, resolveRelativePaths, resolveRulesExtendedViews } from '../view-utils'
|
|
50
48
|
|
|
51
49
|
function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[]): c4.ParsedLikeC4Model {
|
|
50
|
+
// Merge specifications and globals from all documents
|
|
52
51
|
const c4Specification: ParsedAstSpecification = {
|
|
53
52
|
tags: new Set(),
|
|
54
53
|
elements: {},
|
|
55
54
|
relationships: {},
|
|
56
55
|
colors: {}
|
|
57
56
|
}
|
|
58
|
-
|
|
57
|
+
const globals: c4.ModelGlobals = {
|
|
58
|
+
predicates: {},
|
|
59
|
+
dynamicPredicates: {},
|
|
60
|
+
styles: {}
|
|
61
|
+
}
|
|
62
|
+
for (const doc of docs) {
|
|
63
|
+
const {
|
|
64
|
+
c4Specification: spec,
|
|
65
|
+
c4Globals
|
|
66
|
+
} = doc
|
|
67
|
+
|
|
59
68
|
spec.tags.forEach(t => c4Specification.tags.add(t))
|
|
60
69
|
Object.assign(c4Specification.elements, spec.elements)
|
|
61
70
|
Object.assign(c4Specification.relationships, spec.relationships)
|
|
62
71
|
Object.assign(c4Specification.colors, spec.colors)
|
|
63
|
-
|
|
72
|
+
|
|
73
|
+
Object.assign(globals.predicates, c4Globals.predicates)
|
|
74
|
+
Object.assign(globals.dynamicPredicates, c4Globals.dynamicPredicates)
|
|
75
|
+
Object.assign(globals.styles, c4Globals.styles)
|
|
76
|
+
}
|
|
77
|
+
|
|
64
78
|
function resolveLinks(doc: LangiumDocument, links: c4.NonEmptyArray<ParsedLink>) {
|
|
65
79
|
return map(
|
|
66
80
|
links,
|
|
@@ -211,23 +225,6 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
211
225
|
indexBy(prop('id'))
|
|
212
226
|
)
|
|
213
227
|
|
|
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
|
-
|
|
231
228
|
function toC4View(doc: LangiumDocument) {
|
|
232
229
|
const docUri = doc.uri.toString()
|
|
233
230
|
return (parsedAstView: ParsedAstView): c4.LikeC4View => {
|
|
@@ -399,10 +396,9 @@ export class LikeC4ModelBuilder {
|
|
|
399
396
|
|
|
400
397
|
const allViews = [] as c4.ComputedView[]
|
|
401
398
|
for (const view of values(model.views)) {
|
|
402
|
-
const
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
: computeDynamicView(resolvedView, index)
|
|
399
|
+
const result = isElementView(view)
|
|
400
|
+
? computeView(view, index)
|
|
401
|
+
: computeDynamicView(view, index)
|
|
406
402
|
if (!result.isSuccess) {
|
|
407
403
|
logWarnError(result.error)
|
|
408
404
|
continue
|
|
@@ -467,10 +463,9 @@ export class LikeC4ModelBuilder {
|
|
|
467
463
|
return null
|
|
468
464
|
}
|
|
469
465
|
const index = new LikeC4ModelGraph(model)
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
: computeDynamicView(resolvedView, index)
|
|
466
|
+
const result = isElementView(view)
|
|
467
|
+
? computeView(view, index)
|
|
468
|
+
: computeDynamicView(view, index)
|
|
474
469
|
if (!result.isSuccess) {
|
|
475
470
|
logError(result.error)
|
|
476
471
|
return null
|
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
ParsedAstDynamicView,
|
|
12
12
|
ParsedAstElement,
|
|
13
13
|
ParsedAstElementView,
|
|
14
|
+
ParsedAstGlobals,
|
|
14
15
|
ParsedAstRelation,
|
|
15
16
|
ParsedLikeC4LangiumDocument,
|
|
16
17
|
ParsedLink
|
|
@@ -288,6 +289,25 @@ export class LikeC4ModelParser {
|
|
|
288
289
|
const { parseResult, c4Globals } = doc
|
|
289
290
|
|
|
290
291
|
const globals = parseResult.value.globals.filter(isValid)
|
|
292
|
+
|
|
293
|
+
const elRelPredicates = globals.flatMap(r => r.predicates.filter(isValid))
|
|
294
|
+
for (const predicate of elRelPredicates) {
|
|
295
|
+
try {
|
|
296
|
+
const globalPredicateId = predicate.name as c4.GlobalPredicateId
|
|
297
|
+
if (!isTruthy(globalPredicateId)) {
|
|
298
|
+
continue
|
|
299
|
+
}
|
|
300
|
+
if (globalPredicateId in c4Globals.predicates) {
|
|
301
|
+
logger.warn(`Global predicate named "${globalPredicateId}" is already defined`)
|
|
302
|
+
continue
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
this.parseAndStoreGlobalPredicateGroupOrDynamic(predicate, globalPredicateId, c4Globals, isValid)
|
|
306
|
+
} catch (e) {
|
|
307
|
+
logWarnError(e)
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
291
311
|
const styles = globals.flatMap(r => r.styles.filter(isValid))
|
|
292
312
|
for (const style of styles) {
|
|
293
313
|
try {
|
|
@@ -310,12 +330,49 @@ export class LikeC4ModelParser {
|
|
|
310
330
|
}
|
|
311
331
|
}
|
|
312
332
|
|
|
333
|
+
private parseAndStoreGlobalPredicateGroupOrDynamic(
|
|
334
|
+
astRule: ast.GlobalPredicateGroup | ast.GlobalDynamicPredicateGroup,
|
|
335
|
+
id: c4.GlobalPredicateId,
|
|
336
|
+
c4Globals: ParsedAstGlobals,
|
|
337
|
+
isValid: IsValidFn
|
|
338
|
+
) {
|
|
339
|
+
if (ast.isGlobalPredicateGroup(astRule)) {
|
|
340
|
+
const predicates = this.parseGlobalPredicateGroup(astRule, isValid)
|
|
341
|
+
if (predicates.length > 0) {
|
|
342
|
+
c4Globals.predicates[id] = predicates as c4.NonEmptyArray<c4.ViewRulePredicate>
|
|
343
|
+
}
|
|
344
|
+
return
|
|
345
|
+
}
|
|
346
|
+
if (ast.isGlobalDynamicPredicateGroup(astRule)) {
|
|
347
|
+
const predicates = this.parseGlobalDynamicPredicateGroup(astRule, isValid)
|
|
348
|
+
if (predicates.length > 0) {
|
|
349
|
+
c4Globals.dynamicPredicates[id] = predicates as c4.NonEmptyArray<c4.DynamicViewIncludeRule>
|
|
350
|
+
}
|
|
351
|
+
return
|
|
352
|
+
}
|
|
353
|
+
nonexhaustive(astRule)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
private parseGlobalPredicateGroup(
|
|
357
|
+
astRule: ast.GlobalPredicateGroup,
|
|
358
|
+
isValid: IsValidFn
|
|
359
|
+
): c4.ViewRulePredicate[] {
|
|
360
|
+
return astRule.predicates.map(p => this.parseViewRulePredicate(p, isValid))
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
private parseGlobalDynamicPredicateGroup(
|
|
364
|
+
astRule: ast.GlobalDynamicPredicateGroup,
|
|
365
|
+
isValid: IsValidFn
|
|
366
|
+
): c4.DynamicViewIncludeRule[] {
|
|
367
|
+
return astRule.predicates.map(p => this.parseDynamicViewIncludePredicate(p, isValid))
|
|
368
|
+
}
|
|
369
|
+
|
|
313
370
|
private parseGlobalStyleOrGroup(
|
|
314
371
|
astRule: ast.GlobalStyle | ast.GlobalStyleGroup,
|
|
315
372
|
isValid: IsValidFn
|
|
316
373
|
): c4.ViewRuleStyle[] {
|
|
317
374
|
if (ast.isGlobalStyle(astRule)) {
|
|
318
|
-
return [this.
|
|
375
|
+
return [this.parseViewRuleStyle(astRule, isValid)]
|
|
319
376
|
}
|
|
320
377
|
if (ast.isGlobalStyleGroup(astRule)) {
|
|
321
378
|
return astRule.styles.map(s => this.parseViewRuleStyle(s, isValid))
|
|
@@ -323,19 +380,17 @@ export class LikeC4ModelParser {
|
|
|
323
380
|
nonexhaustive(astRule)
|
|
324
381
|
}
|
|
325
382
|
|
|
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
|
-
|
|
333
383
|
private parseViews(doc: ParsedLikeC4LangiumDocument, isValid: IsValidFn) {
|
|
334
384
|
const viewBlocks = doc.parseResult.value.views.filter(v => isValid(v))
|
|
335
385
|
for (const viewBlock of viewBlocks) {
|
|
336
|
-
const localStyles = viewBlock.styles
|
|
337
|
-
|
|
338
|
-
|
|
386
|
+
const localStyles = viewBlock.styles.flatMap(s => {
|
|
387
|
+
try {
|
|
388
|
+
return this.parseViewRuleStyleOrGlobalRef(s, isValid)
|
|
389
|
+
} catch (e) {
|
|
390
|
+
logWarnError(e)
|
|
391
|
+
return []
|
|
392
|
+
}
|
|
393
|
+
})
|
|
339
394
|
|
|
340
395
|
for (const view of viewBlock.views) {
|
|
341
396
|
try {
|
|
@@ -344,8 +399,8 @@ export class LikeC4ModelParser {
|
|
|
344
399
|
}
|
|
345
400
|
doc.c4Views.push(
|
|
346
401
|
ast.isElementView(view)
|
|
347
|
-
? this.parseElementView(view,
|
|
348
|
-
: this.parseDynamicElementView(view,
|
|
402
|
+
? this.parseElementView(view, localStyles, isValid)
|
|
403
|
+
: this.parseDynamicElementView(view, localStyles, isValid)
|
|
349
404
|
)
|
|
350
405
|
} catch (e) {
|
|
351
406
|
logWarnError(e)
|
|
@@ -415,14 +470,14 @@ export class LikeC4ModelParser {
|
|
|
415
470
|
}
|
|
416
471
|
}
|
|
417
472
|
if (ast.isElementKindExpression(astNode)) {
|
|
418
|
-
invariant(astNode.kind, 'ElementKindExpr kind is not resolved: ' + astNode.$cstNode?.text)
|
|
473
|
+
invariant(astNode.kind?.ref, 'ElementKindExpr kind is not resolved: ' + astNode.$cstNode?.text)
|
|
419
474
|
return {
|
|
420
|
-
elementKind: astNode.kind
|
|
475
|
+
elementKind: astNode.kind.ref.name as c4.ElementKind,
|
|
421
476
|
isEqual: astNode.isEqual
|
|
422
477
|
}
|
|
423
478
|
}
|
|
424
479
|
if (ast.isElementTagExpression(astNode)) {
|
|
425
|
-
invariant(astNode.tag, 'ElementTagExpr tag is not resolved: ' + astNode.$cstNode?.text)
|
|
480
|
+
invariant(astNode.tag?.ref, 'ElementTagExpr tag is not resolved: ' + astNode.$cstNode?.text)
|
|
426
481
|
let elementTag = astNode.tag.$refText
|
|
427
482
|
if (elementTag.startsWith('#')) {
|
|
428
483
|
elementTag = elementTag.slice(1)
|
|
@@ -653,6 +708,9 @@ export class LikeC4ModelParser {
|
|
|
653
708
|
if (ast.isViewRulePredicate(astRule)) {
|
|
654
709
|
return this.parseViewRulePredicate(astRule, isValid)
|
|
655
710
|
}
|
|
711
|
+
if (ast.isViewRuleGlobalPredicateRef(astRule)) {
|
|
712
|
+
return this.parseViewRuleGlobalPredicateRef(astRule, isValid)
|
|
713
|
+
}
|
|
656
714
|
if (ast.isViewRuleStyleOrGlobalRef(astRule)) {
|
|
657
715
|
return this.parseViewRuleStyleOrGlobalRef(astRule, isValid)
|
|
658
716
|
}
|
|
@@ -665,7 +723,19 @@ export class LikeC4ModelParser {
|
|
|
665
723
|
nonexhaustive(astRule)
|
|
666
724
|
}
|
|
667
725
|
|
|
668
|
-
private
|
|
726
|
+
private parseViewRuleGlobalPredicateRef(
|
|
727
|
+
astRule: ast.ViewRuleGlobalPredicateRef | ast.DynamicViewGlobalPredicateRef,
|
|
728
|
+
_isValid: IsValidFn
|
|
729
|
+
): c4.ViewRuleGlobalPredicateRef {
|
|
730
|
+
return {
|
|
731
|
+
predicateId: astRule.predicate.$refText as c4.GlobalPredicateId
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
private parseViewRuleStyleOrGlobalRef(
|
|
736
|
+
astRule: ast.ViewRuleStyleOrGlobalRef,
|
|
737
|
+
isValid: IsValidFn
|
|
738
|
+
): c4.ViewRuleStyleOrGlobalRef {
|
|
669
739
|
if (ast.isViewRuleStyle(astRule)) {
|
|
670
740
|
return this.parseViewRuleStyle(astRule, isValid)
|
|
671
741
|
}
|
|
@@ -675,7 +745,7 @@ export class LikeC4ModelParser {
|
|
|
675
745
|
nonexhaustive(astRule)
|
|
676
746
|
}
|
|
677
747
|
|
|
678
|
-
private parseViewRuleStyle(astRule: ast.ViewRuleStyle, isValid: IsValidFn): c4.ViewRuleStyle {
|
|
748
|
+
private parseViewRuleStyle(astRule: ast.ViewRuleStyle | ast.GlobalStyle, isValid: IsValidFn): c4.ViewRuleStyle {
|
|
679
749
|
const styleProps = astRule.props.filter(ast.isStyleProperty)
|
|
680
750
|
const targets = astRule.target
|
|
681
751
|
const notation = astRule.props.find(ast.isNotationProperty)
|
|
@@ -971,6 +1041,9 @@ export class LikeC4ModelParser {
|
|
|
971
1041
|
if (ast.isDynamicViewIncludePredicate(astRule)) {
|
|
972
1042
|
return this.parseDynamicViewIncludePredicate(astRule, isValid)
|
|
973
1043
|
}
|
|
1044
|
+
if (ast.isDynamicViewGlobalPredicateRef(astRule)) {
|
|
1045
|
+
return this.parseViewRuleGlobalPredicateRef(astRule, isValid)
|
|
1046
|
+
}
|
|
974
1047
|
if (ast.isViewRuleStyleOrGlobalRef(astRule)) {
|
|
975
1048
|
return this.parseViewRuleStyleOrGlobalRef(astRule, isValid)
|
|
976
1049
|
}
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
type Fqn,
|
|
6
6
|
invariant,
|
|
7
7
|
isSameHierarchy,
|
|
8
|
+
type ModelGlobals,
|
|
8
9
|
parentFqn,
|
|
9
10
|
type Relation,
|
|
10
11
|
type RelationID
|
|
@@ -14,7 +15,8 @@ import { isArray, isString } from 'remeda'
|
|
|
14
15
|
type Params = {
|
|
15
16
|
elements: Record<Fqn, Element>
|
|
16
17
|
relations: Record<RelationID, Relation>
|
|
17
|
-
//
|
|
18
|
+
// Optional for tests
|
|
19
|
+
globals?: ModelGlobals
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
type RelationEdge = {
|
|
@@ -41,24 +43,33 @@ function intersection<T>(a: Set<T>, b: Set<T>) {
|
|
|
41
43
|
* Subject to change.
|
|
42
44
|
*/
|
|
43
45
|
export class LikeC4ModelGraph {
|
|
44
|
-
#elements = new Map<Fqn, Element>()
|
|
46
|
+
readonly #elements = new Map<Fqn, Element>()
|
|
45
47
|
// Parent element for given FQN
|
|
46
|
-
#parents = new Map<Fqn, Element>()
|
|
48
|
+
readonly #parents = new Map<Fqn, Element>()
|
|
47
49
|
// Children elements for given FQN
|
|
48
|
-
#children = new Map<Fqn, Element[]>()
|
|
49
|
-
#rootElements = new Set<Element>()
|
|
50
|
+
readonly #children = new Map<Fqn, Element[]>()
|
|
51
|
+
readonly #rootElements = new Set<Element>()
|
|
50
52
|
|
|
51
|
-
#relations = new Map<RelationID, Relation>()
|
|
53
|
+
readonly #relations = new Map<RelationID, Relation>()
|
|
52
54
|
// Incoming to an element or its descendants
|
|
53
|
-
#incoming = new MapRelations()
|
|
55
|
+
readonly #incoming = new MapRelations()
|
|
54
56
|
// Outgoing from an element or its descendants
|
|
55
|
-
#outgoing = new MapRelations()
|
|
57
|
+
readonly #outgoing = new MapRelations()
|
|
56
58
|
// Relationships inside the element, among descendants
|
|
57
|
-
#internal = new MapRelations()
|
|
59
|
+
readonly #internal = new MapRelations()
|
|
58
60
|
|
|
59
|
-
#cacheAscendingSiblings = new Map<Fqn, Element[]>()
|
|
61
|
+
readonly #cacheAscendingSiblings = new Map<Fqn, Element[]>()
|
|
60
62
|
|
|
61
|
-
|
|
63
|
+
public readonly globals: ModelGlobals
|
|
64
|
+
|
|
65
|
+
constructor(
|
|
66
|
+
{ elements, relations, globals }: Params
|
|
67
|
+
) {
|
|
68
|
+
this.globals = globals ?? {
|
|
69
|
+
predicates: {},
|
|
70
|
+
dynamicPredicates: {},
|
|
71
|
+
styles: {}
|
|
72
|
+
}
|
|
62
73
|
for (const el of Object.values(elements)) {
|
|
63
74
|
this.addElement(el)
|
|
64
75
|
}
|
|
@@ -11,6 +11,8 @@ import {
|
|
|
11
11
|
type ElementWhereExpr,
|
|
12
12
|
type Expression as C4Expression,
|
|
13
13
|
type Fqn,
|
|
14
|
+
type GlobalStyleID,
|
|
15
|
+
type IconUrl,
|
|
14
16
|
type IncomingExpr as C4IncomingExpr,
|
|
15
17
|
type InOutExpr as C4InOutExpr,
|
|
16
18
|
isElementRef,
|
|
@@ -28,6 +30,7 @@ import {
|
|
|
28
30
|
type Tag,
|
|
29
31
|
type ViewID,
|
|
30
32
|
type ViewRule,
|
|
33
|
+
type ViewRuleGlobalStyle,
|
|
31
34
|
type ViewRuleGroup,
|
|
32
35
|
type ViewRulePredicate,
|
|
33
36
|
type ViewRuleStyle,
|
|
@@ -161,6 +164,7 @@ export const fakeElements = {
|
|
|
161
164
|
id: 'cloud',
|
|
162
165
|
kind: 'system',
|
|
163
166
|
title: 'cloud',
|
|
167
|
+
icon: 'none',
|
|
164
168
|
tags: ['next', 'old']
|
|
165
169
|
}),
|
|
166
170
|
'cloud.backend': el({
|
|
@@ -177,6 +181,7 @@ export const fakeElements = {
|
|
|
177
181
|
'cloud.backend.graphql': el({
|
|
178
182
|
id: 'cloud.backend.graphql',
|
|
179
183
|
kind: 'component',
|
|
184
|
+
icon: 'tech:graphql' as IconUrl,
|
|
180
185
|
title: 'graphql'
|
|
181
186
|
}),
|
|
182
187
|
'email': el({
|
|
@@ -200,12 +205,14 @@ export const fakeElements = {
|
|
|
200
205
|
id: 'cloud.frontend.dashboard',
|
|
201
206
|
kind: 'component',
|
|
202
207
|
title: 'dashboard',
|
|
208
|
+
icon: 'tech:react' as IconUrl,
|
|
203
209
|
tags: ['next']
|
|
204
210
|
}),
|
|
205
211
|
'amazon': el({
|
|
206
212
|
id: 'amazon',
|
|
207
213
|
kind: 'system',
|
|
208
214
|
title: 'amazon',
|
|
215
|
+
icon: 'tech:aws' as IconUrl,
|
|
209
216
|
tags: ['aws']
|
|
210
217
|
}),
|
|
211
218
|
'amazon.s3': el({
|
|
@@ -213,6 +220,7 @@ export const fakeElements = {
|
|
|
213
220
|
kind: 'component',
|
|
214
221
|
title: 's3',
|
|
215
222
|
shape: 'storage',
|
|
223
|
+
icon: 'aws:s3' as IconUrl,
|
|
216
224
|
tags: ['aws', 'storage']
|
|
217
225
|
})
|
|
218
226
|
} satisfies Record<string, Element>
|
|
@@ -331,11 +339,37 @@ export const fakeRelations = [
|
|
|
331
339
|
})
|
|
332
340
|
]
|
|
333
341
|
|
|
342
|
+
export const globalStyles = {
|
|
343
|
+
'mute_old': [{
|
|
344
|
+
targets: [$expr({
|
|
345
|
+
elementTag: 'old' as Tag,
|
|
346
|
+
isEqual: true
|
|
347
|
+
})],
|
|
348
|
+
style: {
|
|
349
|
+
color: 'muted'
|
|
350
|
+
}
|
|
351
|
+
}],
|
|
352
|
+
'red_next': [{
|
|
353
|
+
targets: [$expr({
|
|
354
|
+
elementTag: 'next' as Tag,
|
|
355
|
+
isEqual: true
|
|
356
|
+
})],
|
|
357
|
+
style: {
|
|
358
|
+
color: 'red'
|
|
359
|
+
}
|
|
360
|
+
}]
|
|
361
|
+
} as const
|
|
362
|
+
|
|
334
363
|
export type FakeRelationIds = (typeof fakeRelations)[number]['id']
|
|
335
364
|
|
|
336
365
|
export const fakeModel = new LikeC4ModelGraph({
|
|
337
366
|
elements: fakeElements,
|
|
338
|
-
relations: indexBy(fakeRelations, r => r.id)
|
|
367
|
+
relations: indexBy(fakeRelations, r => r.id),
|
|
368
|
+
globals: {
|
|
369
|
+
predicates: {},
|
|
370
|
+
dynamicPredicates: {},
|
|
371
|
+
styles: globalStyles
|
|
372
|
+
}
|
|
339
373
|
})
|
|
340
374
|
|
|
341
375
|
const emptyView = {
|
|
@@ -510,30 +544,34 @@ type CustomProps = {
|
|
|
510
544
|
}
|
|
511
545
|
export function $include(expr: Expression | C4Expression, props?: CustomProps): ViewRulePredicate {
|
|
512
546
|
let _expr = props?.where ? $where(expr, props.where) : $expr(expr)
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
547
|
+
_expr = props?.with ? $with(_expr, props.with) : _expr
|
|
548
|
+
return {
|
|
549
|
+
include: [_expr]
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
export function $with(expr: C4Expression, props?: CustomProps['with']): C4CustomRelationExpr | C4CustomElementExpr {
|
|
553
|
+
if (isRelationExpression(expr) || isRelationWhere(expr)) {
|
|
554
|
+
return {
|
|
555
|
+
customRelation: {
|
|
556
|
+
relation: expr,
|
|
557
|
+
...props as any
|
|
520
558
|
}
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
559
|
+
}
|
|
560
|
+
} else if (isElementRef(expr) || isElementWhere(expr)) {
|
|
561
|
+
return {
|
|
562
|
+
custom: {
|
|
563
|
+
expr: expr,
|
|
564
|
+
...props as any
|
|
527
565
|
}
|
|
528
566
|
}
|
|
529
567
|
}
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
}
|
|
568
|
+
|
|
569
|
+
throw 'Unsupported type of internal expression'
|
|
533
570
|
}
|
|
534
|
-
export function $exclude(expr: Expression | C4Expression): ViewRulePredicate {
|
|
571
|
+
export function $exclude(expr: Expression | C4Expression, where?: WhereOperator<TestTag, string>): ViewRulePredicate {
|
|
572
|
+
let _expr = where ? $where(expr, where) : $expr(expr)
|
|
535
573
|
return {
|
|
536
|
-
exclude: [
|
|
574
|
+
exclude: [_expr]
|
|
537
575
|
}
|
|
538
576
|
}
|
|
539
577
|
export function $group(groupRules: ViewRuleGroup['groupRules']): ViewRuleGroup {
|
|
@@ -550,6 +588,18 @@ export function $style(element: ElementRefExpr, style: ViewRuleStyle['style']):
|
|
|
550
588
|
}
|
|
551
589
|
}
|
|
552
590
|
|
|
591
|
+
type GlobalStyles = keyof typeof globalStyles
|
|
592
|
+
type GlobalExpr = `style ${GlobalStyles}`
|
|
593
|
+
export function $global(expr: GlobalExpr): ViewRuleGlobalStyle {
|
|
594
|
+
const [_t, id] = expr.split(' ') as [string, string]
|
|
595
|
+
if (_t === 'style') {
|
|
596
|
+
return {
|
|
597
|
+
styleId: id as GlobalStyleID
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
throw new Error('Invalid global expression')
|
|
601
|
+
}
|
|
602
|
+
|
|
553
603
|
export function computeView(
|
|
554
604
|
...args: [FakeElementIds, ViewRule | ViewRule[]] | [ViewRule | ViewRule[]]
|
|
555
605
|
) {
|