@likec4/language-server 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/contrib/likec4.tmLanguage.json +1 -1
- package/package.json +13 -13
- package/src/Rpc.ts +23 -1
- package/src/ast.ts +11 -12
- package/src/generated/ast.ts +101 -31
- package/src/generated/grammar.ts +1 -1
- package/src/like-c4.langium +58 -33
- package/src/lsp/SemanticTokenProvider.ts +12 -14
- package/src/model/fqn-computation.ts +29 -7
- package/src/model/fqn-index.ts +18 -31
- package/src/model/model-builder.ts +13 -9
- package/src/model/model-locator.ts +7 -7
- package/src/model/model-parser.ts +63 -18
- package/src/model-change/changeElementStyle.ts +2 -2
- package/src/model-graph/compute-view/__test__/fixture.ts +51 -23
- package/src/model-graph/compute-view/compute.ts +47 -16
- package/src/model-graph/compute-view/predicates.ts +6 -1
- package/src/model-graph/dynamic-view/compute.ts +2 -2
- package/src/model-graph/utils/{applyElementCustomProperties.ts → applyCustomElementProperties.ts} +5 -3
- package/src/model-graph/utils/applyCustomRelationProperties.ts +50 -0
- package/src/model-graph/utils/applyViewRuleStyles.ts +11 -34
- package/src/model-graph/utils/elementExpressionToPredicate.ts +32 -0
- package/src/references/scope-provider.ts +3 -23
- package/src/validation/dynamic-view-rule.ts +5 -7
- package/src/validation/element.ts +8 -4
- package/src/validation/index.ts +2 -0
- package/src/validation/view-predicates/custom-element-expr.ts +17 -6
- package/src/validation/view-predicates/custom-relation-expr.ts +15 -0
- package/src/validation/view-predicates/index.ts +1 -0
- package/src/view-utils/assignNavigateTo.ts +2 -2
- package/src/view-utils/manual-layout.ts +4 -2
- package/src/view-utils/resolve-extended-views.ts +2 -2
- package/src/view-utils/resolve-relative-paths.ts +3 -3
|
@@ -23,16 +23,15 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
|
|
|
23
23
|
type: SemanticTokenTypes.function
|
|
24
24
|
})
|
|
25
25
|
}
|
|
26
|
-
if (ast.
|
|
27
|
-
|
|
26
|
+
if (ast.isNavigateToProperty(node)) {
|
|
27
|
+
acceptor({
|
|
28
28
|
node,
|
|
29
|
-
property: '
|
|
30
|
-
type: SemanticTokenTypes.
|
|
29
|
+
property: 'key',
|
|
30
|
+
type: SemanticTokenTypes.property
|
|
31
31
|
})
|
|
32
|
-
}
|
|
33
|
-
if (ast.isDescedantsExpr(node) && node.$cstNode) {
|
|
34
32
|
acceptor({
|
|
35
|
-
|
|
33
|
+
node,
|
|
34
|
+
property: 'value',
|
|
36
35
|
type: SemanticTokenTypes.variable,
|
|
37
36
|
modifier: [
|
|
38
37
|
SemanticTokenModifiers.definition,
|
|
@@ -41,7 +40,7 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
|
|
|
41
40
|
})
|
|
42
41
|
return 'prune'
|
|
43
42
|
}
|
|
44
|
-
if (ast.isWildcardExpr(node) && node.$cstNode) {
|
|
43
|
+
if ((ast.isDescedantsExpr(node) || ast.isWildcardExpr(node)) && node.$cstNode) {
|
|
45
44
|
acceptor({
|
|
46
45
|
cst: node.$cstNode,
|
|
47
46
|
type: SemanticTokenTypes.variable,
|
|
@@ -52,7 +51,6 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
|
|
|
52
51
|
})
|
|
53
52
|
return 'prune'
|
|
54
53
|
}
|
|
55
|
-
|
|
56
54
|
if (ast.isElementKindExpr(node)) {
|
|
57
55
|
acceptor({
|
|
58
56
|
node,
|
|
@@ -98,6 +96,7 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
|
|
|
98
96
|
property: 'value',
|
|
99
97
|
type: SemanticTokenTypes.interface
|
|
100
98
|
})
|
|
99
|
+
return
|
|
101
100
|
}
|
|
102
101
|
if (ast.isTag(node)) {
|
|
103
102
|
return acceptor({
|
|
@@ -134,6 +133,7 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
|
|
|
134
133
|
property: 'value',
|
|
135
134
|
type: SemanticTokenTypes.enum
|
|
136
135
|
})
|
|
136
|
+
return 'prune'
|
|
137
137
|
}
|
|
138
138
|
if (ast.isOpacityProperty(node)) {
|
|
139
139
|
acceptor({
|
|
@@ -146,14 +146,12 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
|
|
|
146
146
|
property: 'value',
|
|
147
147
|
type: SemanticTokenTypes.number
|
|
148
148
|
})
|
|
149
|
-
return
|
|
149
|
+
return 'prune'
|
|
150
150
|
}
|
|
151
151
|
if (
|
|
152
152
|
ast.isLinkProperty(node)
|
|
153
153
|
|| ast.isIconProperty(node)
|
|
154
|
-
|| ast.
|
|
155
|
-
|| ast.isRelationStringProperty(node)
|
|
156
|
-
|| ast.isViewStringProperty(node)
|
|
154
|
+
|| ast.isStringProperty(node)
|
|
157
155
|
) {
|
|
158
156
|
acceptor({
|
|
159
157
|
node,
|
|
@@ -165,7 +163,7 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
|
|
|
165
163
|
property: 'value',
|
|
166
164
|
type: SemanticTokenTypes.string
|
|
167
165
|
})
|
|
168
|
-
return
|
|
166
|
+
return 'prune'
|
|
169
167
|
}
|
|
170
168
|
if (ast.isElement(node)) {
|
|
171
169
|
return this.highlightAstElement(node, acceptor)
|
|
@@ -1,14 +1,38 @@
|
|
|
1
1
|
import { AsFqn, type c4, nonexhaustive } from '@likec4/core'
|
|
2
|
-
import { MultiMap } from 'langium'
|
|
2
|
+
import { type AstNodeDescription, type AstNodeLocator, AstUtils, CstUtils, GrammarUtils, MultiMap } from 'langium'
|
|
3
3
|
import { isEmpty, isNullish as isNil } from 'remeda'
|
|
4
4
|
import { ast, ElementOps, type LikeC4LangiumDocument } from '../ast'
|
|
5
5
|
import { getFqnElementRef } from '../elementRef'
|
|
6
6
|
import type { LikeC4Services } from '../module'
|
|
7
7
|
|
|
8
|
+
const { findNodeForProperty } = GrammarUtils
|
|
9
|
+
const { toDocumentSegment } = CstUtils
|
|
10
|
+
const { getDocument } = AstUtils
|
|
11
|
+
|
|
8
12
|
type TraversePair = [el: ast.Element | ast.ExtendElement | ast.Relation, parent: c4.Fqn | null]
|
|
9
13
|
|
|
14
|
+
function toAstNodeDescription(
|
|
15
|
+
locator: AstNodeLocator,
|
|
16
|
+
entry: ast.Element,
|
|
17
|
+
doc: LikeC4LangiumDocument
|
|
18
|
+
): AstNodeDescription {
|
|
19
|
+
const $cstNode = findNodeForProperty(entry.$cstNode, 'name')
|
|
20
|
+
return {
|
|
21
|
+
documentUri: doc.uri,
|
|
22
|
+
name: entry.name,
|
|
23
|
+
...(entry.$cstNode && {
|
|
24
|
+
selectionSegment: toDocumentSegment(entry.$cstNode)
|
|
25
|
+
}),
|
|
26
|
+
...($cstNode && {
|
|
27
|
+
nameSegment: toDocumentSegment($cstNode)
|
|
28
|
+
}),
|
|
29
|
+
path: locator.getAstNodePath(entry),
|
|
30
|
+
type: ast.Element
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
10
34
|
export function computeDocumentFqn(document: LikeC4LangiumDocument, services: LikeC4Services) {
|
|
11
|
-
const
|
|
35
|
+
const c4fqnIndex = (document.c4fqnIndex = new MultiMap())
|
|
12
36
|
const elements = document.parseResult.value.models.flatMap(m => m.elements)
|
|
13
37
|
if (elements.length === 0) {
|
|
14
38
|
return
|
|
@@ -30,11 +54,9 @@ export function computeDocumentFqn(document: LikeC4LangiumDocument, services: Li
|
|
|
30
54
|
}
|
|
31
55
|
if (ast.isElement(el)) {
|
|
32
56
|
const fqn = AsFqn(el.name, parent)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
path,
|
|
37
|
-
name: el.name
|
|
57
|
+
c4fqnIndex.add(fqn, {
|
|
58
|
+
...toAstNodeDescription(locator, el, document),
|
|
59
|
+
fqn
|
|
38
60
|
})
|
|
39
61
|
ElementOps.writeId(el, fqn)
|
|
40
62
|
if (!isNil(el.body) && !isEmpty(el.body.elements)) {
|
package/src/model/fqn-index.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Fqn } from '@likec4/core'
|
|
2
2
|
import { nameFromFqn, parentFqn } from '@likec4/core'
|
|
3
|
-
import type { LangiumDocuments, Stream } from 'langium'
|
|
3
|
+
import type { AstNodeDescription, LangiumDocuments, Stream } from 'langium'
|
|
4
4
|
import { DocumentState, DONE_RESULT, MultiMap, stream, StreamImpl } from 'langium'
|
|
5
|
-
import type { ast, FqnIndexedDocument } from '../ast'
|
|
5
|
+
import type { ast, DocFqnIndexAstNodeDescription, FqnIndexedDocument } from '../ast'
|
|
6
6
|
import { ElementOps, isFqnIndexedDocument, isLikeC4LangiumDocument } from '../ast'
|
|
7
7
|
import { logError, logger } from '../logger'
|
|
8
8
|
import type { LikeC4Services } from '../module'
|
|
@@ -17,8 +17,6 @@ export interface FqnIndexEntry {
|
|
|
17
17
|
path: string
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
const True = () => true
|
|
21
|
-
|
|
22
20
|
export class FqnIndex {
|
|
23
21
|
protected langiumDocuments: LangiumDocuments
|
|
24
22
|
|
|
@@ -31,7 +29,7 @@ export class FqnIndex {
|
|
|
31
29
|
logger.debug(`[FqnIndex] onIndexedContent ${docs.length}:\n` + printDocs(docs))
|
|
32
30
|
for (const doc of docs) {
|
|
33
31
|
if (isLikeC4LangiumDocument(doc)) {
|
|
34
|
-
delete doc.
|
|
32
|
+
delete doc.c4fqnIndex
|
|
35
33
|
delete doc.c4Elements
|
|
36
34
|
delete doc.c4Specification
|
|
37
35
|
delete doc.c4Relations
|
|
@@ -53,18 +51,13 @@ export class FqnIndex {
|
|
|
53
51
|
return this.langiumDocuments.all.filter(isFqnIndexedDocument)
|
|
54
52
|
}
|
|
55
53
|
|
|
56
|
-
private entries(filterByFqn
|
|
57
|
-
return this.documents.flatMap(doc =>
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return []
|
|
66
|
-
})
|
|
67
|
-
)
|
|
54
|
+
private entries(filterByFqn?: (fqn: Fqn) => boolean): Stream<DocFqnIndexAstNodeDescription> {
|
|
55
|
+
return this.documents.flatMap(doc => {
|
|
56
|
+
if (filterByFqn) {
|
|
57
|
+
return doc.c4fqnIndex.keys().filter(filterByFqn).flatMap(fqn => doc.c4fqnIndex.get(fqn))
|
|
58
|
+
}
|
|
59
|
+
return doc.c4fqnIndex.values()
|
|
60
|
+
})
|
|
68
61
|
}
|
|
69
62
|
|
|
70
63
|
public getFqn(el: ast.Element): Fqn | null {
|
|
@@ -83,22 +76,16 @@ export class FqnIndex {
|
|
|
83
76
|
// return fqn
|
|
84
77
|
}
|
|
85
78
|
|
|
86
|
-
public byFqn(fqn: Fqn): Stream<
|
|
79
|
+
public byFqn(fqn: Fqn): Stream<AstNodeDescription> {
|
|
87
80
|
return this.documents.flatMap(doc => {
|
|
88
|
-
return doc.
|
|
89
|
-
const el = entry.el.deref()
|
|
90
|
-
if (el) {
|
|
91
|
-
return { fqn, el, doc, path: entry.path, name: entry.name }
|
|
92
|
-
}
|
|
93
|
-
return []
|
|
94
|
-
})
|
|
81
|
+
return doc.c4fqnIndex.get(fqn)
|
|
95
82
|
})
|
|
96
83
|
}
|
|
97
84
|
|
|
98
|
-
public directChildrenOf(parent: Fqn): Stream<
|
|
85
|
+
public directChildrenOf(parent: Fqn): Stream<AstNodeDescription> {
|
|
99
86
|
return stream([parent]).flatMap(_parent => {
|
|
100
87
|
const children = this.entries(fqn => parentFqn(fqn) === _parent)
|
|
101
|
-
.map((entry)
|
|
88
|
+
.map((entry) => [entry.name, entry] as [string, AstNodeDescription])
|
|
102
89
|
.toArray()
|
|
103
90
|
if (children.length === 0) {
|
|
104
91
|
return []
|
|
@@ -113,15 +100,15 @@ export class FqnIndex {
|
|
|
113
100
|
/**
|
|
114
101
|
* Returns descedant elements with unique names in the scope
|
|
115
102
|
*/
|
|
116
|
-
public uniqueDescedants(parent: Fqn): Stream<
|
|
103
|
+
public uniqueDescedants(parent: Fqn): Stream<AstNodeDescription> {
|
|
117
104
|
return new StreamImpl(
|
|
118
105
|
() => {
|
|
119
106
|
const prefix = `${parent}.`
|
|
120
107
|
|
|
121
108
|
const childrenNames = new Set<string>()
|
|
122
|
-
const descedants = [] as
|
|
109
|
+
const descedants = [] as AstNodeDescription[]
|
|
123
110
|
|
|
124
|
-
const nested = new MultiMap<string,
|
|
111
|
+
const nested = new MultiMap<string, AstNodeDescription>()
|
|
125
112
|
|
|
126
113
|
this.entries(f => f.startsWith(prefix)).forEach(e => {
|
|
127
114
|
const name = nameFromFqn(e.fqn)
|
|
@@ -154,7 +141,7 @@ export class FqnIndex {
|
|
|
154
141
|
if (iterator) {
|
|
155
142
|
return iterator.next()
|
|
156
143
|
}
|
|
157
|
-
return DONE_RESULT as IteratorResult<
|
|
144
|
+
return DONE_RESULT as IteratorResult<AstNodeDescription>
|
|
158
145
|
}
|
|
159
146
|
)
|
|
160
147
|
}
|
|
@@ -2,9 +2,9 @@ import {
|
|
|
2
2
|
type c4,
|
|
3
3
|
compareByFqnHierarchically,
|
|
4
4
|
isElementView,
|
|
5
|
-
|
|
5
|
+
isScopedElementView,
|
|
6
6
|
parentFqn,
|
|
7
|
-
type
|
|
7
|
+
type ScopedElementView,
|
|
8
8
|
type ViewID
|
|
9
9
|
} from '@likec4/core'
|
|
10
10
|
import { deepEqual as eq } from 'fast-equals'
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
sort,
|
|
27
27
|
values
|
|
28
28
|
} from 'remeda'
|
|
29
|
-
import { type CancellationToken, Disposable } from 'vscode-languageserver'
|
|
29
|
+
import { type CancellationToken, Disposable } from 'vscode-languageserver-protocol'
|
|
30
30
|
import type {
|
|
31
31
|
ParsedAstElement,
|
|
32
32
|
ParsedAstRelation,
|
|
@@ -107,7 +107,7 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
107
107
|
|
|
108
108
|
const elements = pipe(
|
|
109
109
|
docs,
|
|
110
|
-
flatMap(d => d.c4Elements
|
|
110
|
+
flatMap(d => map(d.c4Elements, toModelElement(d))),
|
|
111
111
|
filter(isTruthy),
|
|
112
112
|
sort(compareByFqnHierarchically),
|
|
113
113
|
reduce(
|
|
@@ -140,10 +140,13 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
140
140
|
...model
|
|
141
141
|
}: ParsedAstRelation): c4.Relation | null => {
|
|
142
142
|
if (isNullish(elements[source]) || isNullish(elements[target])) {
|
|
143
|
+
logger.warn(
|
|
144
|
+
`Invalid relation ${id}, source: ${source}(${!!elements[source]}), target: ${target}(${!!elements[target]})`
|
|
145
|
+
)
|
|
143
146
|
return null
|
|
144
147
|
}
|
|
145
148
|
|
|
146
|
-
if (
|
|
149
|
+
if (!isNullish(kind) && kind in c4Specification.relationships) {
|
|
147
150
|
return {
|
|
148
151
|
...(links && { links: resolveLinks(doc, links) }),
|
|
149
152
|
...c4Specification.relationships[kind],
|
|
@@ -165,14 +168,15 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
165
168
|
}
|
|
166
169
|
|
|
167
170
|
const relations = pipe(
|
|
168
|
-
|
|
171
|
+
docs,
|
|
172
|
+
flatMap(d => map(d.c4Relations, toModelRelation(d))),
|
|
169
173
|
filter(isTruthy),
|
|
170
174
|
mapToObj(r => [r.id, r])
|
|
171
175
|
)
|
|
172
176
|
|
|
173
177
|
const toC4View = (doc: LangiumDocument) => {
|
|
174
178
|
const docUri = doc.uri.toString()
|
|
175
|
-
return (parsedAstView: ParsedAstView): c4.
|
|
179
|
+
return (parsedAstView: ParsedAstView): c4.LikeC4View => {
|
|
176
180
|
let {
|
|
177
181
|
id,
|
|
178
182
|
title,
|
|
@@ -188,7 +192,7 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
188
192
|
} = parsedAstView
|
|
189
193
|
|
|
190
194
|
if (parsedAstView.__ === 'element' && isNullish(title) && 'viewOf' in parsedAstView) {
|
|
191
|
-
title
|
|
195
|
+
title = elements[parsedAstView.viewOf]?.title ?? null
|
|
192
196
|
}
|
|
193
197
|
|
|
194
198
|
if (isNullish(title) && id === 'index') {
|
|
@@ -359,7 +363,7 @@ export class LikeC4ModelBuilder {
|
|
|
359
363
|
}
|
|
360
364
|
|
|
361
365
|
const allElementViews = values(model.views).filter(
|
|
362
|
-
(v): v is
|
|
366
|
+
(v): v is ScopedElementView => isScopedElementView(v) && v.id !== viewId
|
|
363
367
|
)
|
|
364
368
|
|
|
365
369
|
let computedView = result.view
|
|
@@ -33,18 +33,18 @@ export class LikeC4ModelLocator {
|
|
|
33
33
|
return doc.c4Elements.find(e => e.id === fqn) ?? null
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
public locateElement(fqn: c4.Fqn,
|
|
36
|
+
public locateElement(fqn: c4.Fqn, _property = 'name'): Location | null {
|
|
37
37
|
const entry = this.fqnIndex.byFqn(fqn).head()
|
|
38
38
|
if (!entry) {
|
|
39
39
|
return null
|
|
40
40
|
}
|
|
41
|
-
const propertyNode = findNodeForProperty(entry.el.$cstNode, property) ?? entry.el.$cstNode
|
|
42
|
-
if (!propertyNode) {
|
|
43
|
-
|
|
44
|
-
}
|
|
41
|
+
// const propertyNode = findNodeForProperty(entry.el.$cstNode, property) ?? entry.el.$cstNode
|
|
42
|
+
// if (!propertyNode) {
|
|
43
|
+
// return null
|
|
44
|
+
// }
|
|
45
45
|
return {
|
|
46
|
-
uri: entry.
|
|
47
|
-
range:
|
|
46
|
+
uri: entry.documentUri.toString(),
|
|
47
|
+
range: entry.nameSegment?.range!
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type c4, InvalidModelError, invariant, isNonEmptyArray, nonexhaustive } from '@likec4/core'
|
|
2
2
|
import type { AstNode, LangiumDocument } from 'langium'
|
|
3
3
|
import { AstUtils, CstUtils } from 'langium'
|
|
4
|
-
import { isTruthy } from 'remeda'
|
|
4
|
+
import { isTruthy, mapToObj } from 'remeda'
|
|
5
5
|
import stripIndent from 'strip-indent'
|
|
6
6
|
import type {
|
|
7
7
|
ChecksFromDiagnostics,
|
|
@@ -37,14 +37,14 @@ const { getDocument } = AstUtils
|
|
|
37
37
|
export type ModelParsedListener = () => void
|
|
38
38
|
|
|
39
39
|
function toSingleLine<T extends string | undefined>(str: T): T {
|
|
40
|
-
return (str ? removeIndent(str).split('\n').join(' ') : undefined) as T
|
|
40
|
+
return (isTruthy(str) ? removeIndent(str).split('\n').join(' ') : undefined) as T
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
function removeIndent<T extends string | undefined>(str: T): T {
|
|
44
|
-
return (str ? stripIndent(str).trim() : undefined) as T
|
|
44
|
+
return (isTruthy(str) ? stripIndent(str).trim() : undefined) as T
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
type IsValidFn = ChecksFromDiagnostics['isValid']
|
|
47
|
+
export type IsValidFn = ChecksFromDiagnostics['isValid']
|
|
48
48
|
|
|
49
49
|
export class LikeC4ModelParser {
|
|
50
50
|
private fqnIndex: FqnIndex
|
|
@@ -142,11 +142,11 @@ export class LikeC4ModelParser {
|
|
|
142
142
|
|
|
143
143
|
let [title, description, technology] = astNode.props ?? []
|
|
144
144
|
|
|
145
|
-
const bodyProps = astNode.body?.props.filter(ast.isElementStringProperty) ?? []
|
|
145
|
+
const bodyProps = mapToObj(astNode.body?.props.filter(ast.isElementStringProperty) ?? [], p => [p.key, p.value])
|
|
146
146
|
|
|
147
|
-
title = toSingleLine(title ?? bodyProps.
|
|
148
|
-
description = removeIndent(description ?? bodyProps.
|
|
149
|
-
technology = toSingleLine(technology ?? bodyProps.
|
|
147
|
+
title = toSingleLine(title ?? bodyProps.title)
|
|
148
|
+
description = removeIndent(description ?? bodyProps.description)
|
|
149
|
+
technology = toSingleLine(technology ?? bodyProps.technology)
|
|
150
150
|
|
|
151
151
|
const links = astNode.body?.props.filter(ast.isLinkProperty).map(p => p.value)
|
|
152
152
|
|
|
@@ -171,7 +171,7 @@ export class LikeC4ModelParser {
|
|
|
171
171
|
const links = astNode.body?.props.filter(ast.isLinkProperty).map(p => p.value)
|
|
172
172
|
const kind = astNode.kind?.ref?.name as c4.RelationshipKind
|
|
173
173
|
const astPath = this.getAstNodePath(astNode)
|
|
174
|
-
const title =
|
|
174
|
+
const title = removeIndent(
|
|
175
175
|
astNode.title ?? astNode.body?.props.find((p): p is ast.RelationStringProperty => p.key === 'title')?.value
|
|
176
176
|
) ?? ''
|
|
177
177
|
const styleProp = astNode.body?.props.find(ast.isRelationStyleProperty)
|
|
@@ -321,12 +321,11 @@ export class LikeC4ModelParser {
|
|
|
321
321
|
}
|
|
322
322
|
|
|
323
323
|
private parsePredicateExpr(astNode: ast.ViewRulePredicateExpr): c4.Expression {
|
|
324
|
+
if (ast.isCustomRelationExpr(astNode)) {
|
|
325
|
+
return this.parseCustomRelationExpr(astNode)
|
|
326
|
+
}
|
|
324
327
|
if (ast.isRelationExpr(astNode)) {
|
|
325
|
-
return
|
|
326
|
-
source: this.parseElementExpr(astNode.source),
|
|
327
|
-
target: this.parseElementExpr(astNode.target),
|
|
328
|
-
isBidirectional: astNode.isBidirectional
|
|
329
|
-
}
|
|
328
|
+
return this.parseRelationExpr(astNode)
|
|
330
329
|
}
|
|
331
330
|
if (ast.isInOutExpr(astNode)) {
|
|
332
331
|
return {
|
|
@@ -352,6 +351,48 @@ export class LikeC4ModelParser {
|
|
|
352
351
|
nonexhaustive(astNode)
|
|
353
352
|
}
|
|
354
353
|
|
|
354
|
+
private parseCustomRelationExpr(astNode: ast.CustomRelationExpr): c4.CustomRelationExpr {
|
|
355
|
+
const relation = this.parseRelationExpr(astNode.relation)
|
|
356
|
+
const props = astNode.body?.props ?? []
|
|
357
|
+
return props.reduce(
|
|
358
|
+
(acc, prop) => {
|
|
359
|
+
if (ast.isRelationStringProperty(prop)) {
|
|
360
|
+
const value = removeIndent(prop.value)
|
|
361
|
+
if (isTruthy(value)) {
|
|
362
|
+
acc.customRelation['title'] = value
|
|
363
|
+
}
|
|
364
|
+
return acc
|
|
365
|
+
}
|
|
366
|
+
if (ast.isArrowProperty(prop)) {
|
|
367
|
+
acc.customRelation[prop.key] = prop.value
|
|
368
|
+
return acc
|
|
369
|
+
}
|
|
370
|
+
if (ast.isColorProperty(prop)) {
|
|
371
|
+
acc.customRelation[prop.key] = prop.value
|
|
372
|
+
return acc
|
|
373
|
+
}
|
|
374
|
+
if (ast.isLineProperty(prop)) {
|
|
375
|
+
acc.customRelation[prop.key] = prop.value
|
|
376
|
+
return acc
|
|
377
|
+
}
|
|
378
|
+
nonexhaustive(prop)
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
customRelation: {
|
|
382
|
+
relation
|
|
383
|
+
}
|
|
384
|
+
} as c4.CustomRelationExpr
|
|
385
|
+
)
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
private parseRelationExpr(astNode: ast.RelationExpr): c4.RelationExpr {
|
|
389
|
+
return {
|
|
390
|
+
source: this.parseElementExpr(astNode.source),
|
|
391
|
+
target: this.parseElementExpr(astNode.target),
|
|
392
|
+
isBidirectional: astNode.isBidirectional
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
355
396
|
private parseViewRule(astRule: ast.ViewRule, isValid: IsValidFn): c4.ViewRule {
|
|
356
397
|
if (ast.isIncludePredicate(astRule) || ast.isExcludePredicate(astRule)) {
|
|
357
398
|
const exprs = astRule.expressions.flatMap(n => {
|
|
@@ -398,18 +439,22 @@ export class LikeC4ModelParser {
|
|
|
398
439
|
if (!targetEl) {
|
|
399
440
|
throw new Error('Invalid reference to target')
|
|
400
441
|
}
|
|
442
|
+
const title = removeIndent(node.title) ?? null
|
|
401
443
|
let source = this.resolveFqn(sourceEl)
|
|
402
444
|
let target = this.resolveFqn(targetEl)
|
|
403
445
|
if (node.isBackward) {
|
|
404
|
-
|
|
446
|
+
return {
|
|
447
|
+
source: target,
|
|
448
|
+
target: source,
|
|
449
|
+
title,
|
|
450
|
+
isBackward: true
|
|
451
|
+
}
|
|
405
452
|
}
|
|
406
453
|
|
|
407
|
-
const title = toSingleLine(node.title) ?? null
|
|
408
454
|
return {
|
|
409
455
|
source,
|
|
410
456
|
target,
|
|
411
|
-
title
|
|
412
|
-
isBackward: node.isBackward
|
|
457
|
+
title
|
|
413
458
|
}
|
|
414
459
|
}
|
|
415
460
|
|
|
@@ -12,7 +12,7 @@ const asViewStyleRule = (target: string, style: ViewChanges.ChangeElementStyle['
|
|
|
12
12
|
const indentStr = indent > 0 ? ' '.repeat(indent) : ''
|
|
13
13
|
return [
|
|
14
14
|
indentStr + `style ${target} {`,
|
|
15
|
-
...entries
|
|
15
|
+
...entries(style).map(([key, value]) =>
|
|
16
16
|
indentStr + ` ${key} ${key === 'opacity' ? value.toString() + '%' : value}`
|
|
17
17
|
),
|
|
18
18
|
indentStr + `}`
|
|
@@ -125,7 +125,7 @@ export function changeElementStyle(services: LikeC4Services, {
|
|
|
125
125
|
for (const { rule } of existing) {
|
|
126
126
|
const ruleCstNode = rule.$cstNode
|
|
127
127
|
invariant(ruleCstNode, 'RuleCstNode not found')
|
|
128
|
-
for (const [key, _value] of entries
|
|
128
|
+
for (const [key, _value] of entries(style)) {
|
|
129
129
|
const value = key === 'opacity' ? _value.toString() + '%' : _value
|
|
130
130
|
const ruleProp = rule.styleprops.find(p => p.key === key)
|
|
131
131
|
// replace existing property
|