@likec4/language-server 1.8.1 → 1.9.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/dist/browser.cjs +21 -0
- package/dist/browser.d.cts +22 -0
- package/dist/browser.d.mts +22 -0
- package/dist/browser.d.ts +22 -0
- package/dist/browser.mjs +19 -0
- package/dist/index.cjs +10 -0
- package/dist/index.d.cts +18 -0
- package/dist/index.d.mts +18 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.mjs +1 -0
- package/dist/likec4lib.cjs +961 -0
- package/dist/likec4lib.d.cts +6 -0
- package/dist/likec4lib.d.mts +6 -0
- package/dist/likec4lib.d.ts +6 -0
- package/dist/likec4lib.mjs +957 -0
- package/dist/model-graph/index.cjs +10 -0
- package/dist/model-graph/index.d.cts +79 -0
- package/dist/model-graph/index.d.mts +79 -0
- package/dist/model-graph/index.d.ts +79 -0
- package/dist/model-graph/index.mjs +1 -0
- package/dist/node.cjs +18 -0
- package/dist/node.d.cts +20 -0
- package/dist/node.d.mts +20 -0
- package/dist/node.d.ts +20 -0
- package/dist/node.mjs +16 -0
- package/dist/protocol.cjs +25 -0
- package/dist/protocol.d.cts +43 -0
- package/dist/protocol.d.mts +43 -0
- package/dist/protocol.d.ts +43 -0
- package/dist/protocol.mjs +17 -0
- package/dist/shared/language-server.86lmJ8ZN.d.cts +1194 -0
- package/dist/shared/language-server.B1TZgyoH.cjs +5371 -0
- package/dist/shared/language-server.CCB4ESN5.mjs +1606 -0
- package/dist/shared/language-server.CFTY6j4e.d.mts +1194 -0
- package/dist/shared/language-server.D0bOlrCi.cjs +1619 -0
- package/dist/shared/language-server.Q-wtPShM.mjs +5360 -0
- package/dist/shared/language-server.RjhrBZS0.d.ts +1194 -0
- package/package.json +35 -20
- package/src/ast.ts +40 -30
- package/src/browser.ts +0 -3
- package/src/elementRef.ts +1 -1
- package/src/generated/ast.ts +67 -3
- package/src/generated/grammar.ts +1 -1
- package/src/generated-lib/icons.ts +1 -1
- package/src/like-c4.langium +16 -2
- package/src/likec4lib.ts +2 -3
- package/src/logger.ts +9 -1
- package/src/lsp/RenameProvider.ts +8 -0
- package/src/lsp/SemanticTokenProvider.ts +19 -1
- package/src/lsp/index.ts +1 -0
- package/src/model/fqn-computation.ts +33 -23
- package/src/model/fqn-index.ts +5 -20
- package/src/model/model-builder.ts +147 -90
- package/src/model/model-locator.ts +1 -1
- package/src/model/model-parser-where.ts +3 -2
- package/src/model/model-parser.ts +57 -19
- package/src/model-graph/LikeC4ModelGraph.ts +42 -21
- package/src/model-graph/compute-view/__test__/fixture.ts +16 -14
- package/src/model-graph/compute-view/compute.ts +9 -6
- package/src/model-graph/compute-view/predicates.ts +3 -3
- package/src/model-graph/dynamic-view/__test__/fixture.ts +1 -0
- package/src/model-graph/dynamic-view/compute.ts +2 -1
- package/src/model-graph/utils/elementExpressionToPredicate.ts +1 -1
- package/src/model-graph/utils/sortNodes.ts +2 -6
- package/src/module.ts +23 -3
- package/src/protocol.ts +4 -5
- package/src/references/scope-computation.ts +10 -1
- package/src/references/scope-provider.ts +2 -1
- package/src/shared/NodeKindProvider.ts +73 -34
- package/src/test/setup.ts +3 -8
- package/src/utils/graphlib.ts +11 -0
- package/src/view-utils/manual-layout.ts +1 -1
- package/src/view-utils/resolve-extended-views.ts +19 -10
- package/src/view-utils/resolve-relative-paths.ts +5 -7
- package/src/view-utils/view-hash.ts +1 -1
- package/src/reset.d.ts +0 -2
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
|
-
type c4,
|
|
3
2
|
compareByFqnHierarchically,
|
|
3
|
+
compareRelations,
|
|
4
|
+
computeColorValues,
|
|
5
|
+
type CustomColorDefinitions,
|
|
4
6
|
isElementView,
|
|
5
7
|
isScopedElementView,
|
|
6
8
|
parentFqn,
|
|
7
9
|
type ScopedElementView,
|
|
8
10
|
type ViewID
|
|
9
11
|
} from '@likec4/core'
|
|
12
|
+
import type * as c4 from '@likec4/core'
|
|
10
13
|
import { deepEqual as eq } from 'fast-equals'
|
|
11
14
|
import type { Cancellation, LangiumDocument, LangiumDocuments, URI, WorkspaceCache } from 'langium'
|
|
12
15
|
import { Disposable, DocumentState, interruptAndCheck } from 'langium'
|
|
@@ -15,14 +18,18 @@ import {
|
|
|
15
18
|
find,
|
|
16
19
|
flatMap,
|
|
17
20
|
forEach,
|
|
21
|
+
indexBy,
|
|
22
|
+
isNonNullish,
|
|
18
23
|
isNullish,
|
|
19
24
|
isNumber,
|
|
20
25
|
isTruthy,
|
|
21
26
|
map,
|
|
22
27
|
mapToObj,
|
|
28
|
+
mapValues,
|
|
23
29
|
pipe,
|
|
24
30
|
prop,
|
|
25
31
|
reduce,
|
|
32
|
+
reverse,
|
|
26
33
|
sort,
|
|
27
34
|
values
|
|
28
35
|
} from 'remeda'
|
|
@@ -40,26 +47,40 @@ import type { LikeC4Services } from '../module'
|
|
|
40
47
|
import { printDocs } from '../utils/printDocs'
|
|
41
48
|
import { assignNavigateTo, resolveRelativePaths, resolveRulesExtendedViews } from '../view-utils'
|
|
42
49
|
|
|
43
|
-
function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[]): c4.
|
|
50
|
+
function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[]): c4.ParsedLikeC4Model {
|
|
44
51
|
const c4Specification: ParsedAstSpecification = {
|
|
45
|
-
|
|
46
|
-
|
|
52
|
+
tags: new Set(),
|
|
53
|
+
elements: {},
|
|
54
|
+
relationships: {},
|
|
55
|
+
colors: {}
|
|
47
56
|
}
|
|
48
57
|
forEach(map(docs, prop('c4Specification')), spec => {
|
|
49
|
-
|
|
58
|
+
spec.tags.forEach(t => c4Specification.tags.add(t))
|
|
59
|
+
Object.assign(c4Specification.elements, spec.elements)
|
|
50
60
|
Object.assign(c4Specification.relationships, spec.relationships)
|
|
61
|
+
Object.assign(c4Specification.colors, spec.colors)
|
|
51
62
|
})
|
|
52
63
|
const resolveLinks = (doc: LangiumDocument, links: c4.NonEmptyArray<c4.Link>) => {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
64
|
+
try {
|
|
65
|
+
return links.map(l => ({
|
|
66
|
+
...l,
|
|
67
|
+
url: services.lsp.DocumentLinkProvider.resolveLink(doc, l.url)
|
|
68
|
+
})) as c4.NonEmptyArray<c4.Link>
|
|
69
|
+
} catch (e) {
|
|
70
|
+
logWarnError(e)
|
|
71
|
+
return null
|
|
72
|
+
}
|
|
57
73
|
}
|
|
58
74
|
|
|
75
|
+
const customColorDefinitions: CustomColorDefinitions = mapValues(
|
|
76
|
+
c4Specification.colors,
|
|
77
|
+
c => computeColorValues(c.color)
|
|
78
|
+
)
|
|
79
|
+
|
|
59
80
|
const toModelElement = (doc: LangiumDocument) => {
|
|
60
81
|
return ({
|
|
61
82
|
tags,
|
|
62
|
-
links,
|
|
83
|
+
links: unresolvedLinks,
|
|
63
84
|
style: {
|
|
64
85
|
color,
|
|
65
86
|
shape,
|
|
@@ -75,11 +96,12 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
75
96
|
metadata
|
|
76
97
|
}: ParsedAstElement): c4.Element | null => {
|
|
77
98
|
try {
|
|
78
|
-
const __kind = c4Specification.
|
|
99
|
+
const __kind = c4Specification.elements[kind]
|
|
79
100
|
if (!__kind) {
|
|
80
101
|
logger.warn(`No kind '${kind}' found for ${id}`)
|
|
81
102
|
return null
|
|
82
103
|
}
|
|
104
|
+
const links = unresolvedLinks ? resolveLinks(doc, unresolvedLinks) : null
|
|
83
105
|
color ??= __kind.style.color
|
|
84
106
|
shape ??= __kind.style.shape
|
|
85
107
|
icon ??= __kind.style.icon
|
|
@@ -96,7 +118,7 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
96
118
|
...(border && { border }),
|
|
97
119
|
...(isNumber(opacity) && { opacity })
|
|
98
120
|
},
|
|
99
|
-
links
|
|
121
|
+
links,
|
|
100
122
|
tags: tags ?? null,
|
|
101
123
|
technology: technology ?? null,
|
|
102
124
|
description: description ?? null,
|
|
@@ -115,6 +137,7 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
115
137
|
docs,
|
|
116
138
|
flatMap(d => map(d.c4Elements, toModelElement(d))),
|
|
117
139
|
filter(isTruthy),
|
|
140
|
+
// sort from root elements to nested, so that parent is always present
|
|
118
141
|
sort(compareByFqnHierarchically),
|
|
119
142
|
reduce(
|
|
120
143
|
(acc, el) => {
|
|
@@ -123,15 +146,10 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
123
146
|
logWarnError(`No parent found for ${el.id}`)
|
|
124
147
|
return acc
|
|
125
148
|
}
|
|
126
|
-
if (el.id in acc) {
|
|
127
|
-
// should not happen, as validated
|
|
128
|
-
logWarnError(`Duplicate element id: ${el.id}`)
|
|
129
|
-
return acc
|
|
130
|
-
}
|
|
131
149
|
acc[el.id] = el
|
|
132
150
|
return acc
|
|
133
151
|
},
|
|
134
|
-
{} as c4.
|
|
152
|
+
{} as c4.ParsedLikeC4Model['elements']
|
|
135
153
|
)
|
|
136
154
|
)
|
|
137
155
|
|
|
@@ -141,7 +159,7 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
141
159
|
source,
|
|
142
160
|
target,
|
|
143
161
|
kind,
|
|
144
|
-
links,
|
|
162
|
+
links: unresolvedLinks,
|
|
145
163
|
id,
|
|
146
164
|
...model
|
|
147
165
|
}: ParsedAstRelation): c4.Relation | null => {
|
|
@@ -151,12 +169,13 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
151
169
|
)
|
|
152
170
|
return null
|
|
153
171
|
}
|
|
172
|
+
const links = unresolvedLinks ? resolveLinks(doc, unresolvedLinks) : null
|
|
154
173
|
|
|
155
|
-
if (
|
|
174
|
+
if (isNonNullish(kind) && kind in c4Specification.relationships) {
|
|
156
175
|
return {
|
|
157
|
-
...(links && { links: resolveLinks(doc, links) }),
|
|
158
176
|
...c4Specification.relationships[kind],
|
|
159
177
|
...model,
|
|
178
|
+
...(links && { links }),
|
|
160
179
|
source,
|
|
161
180
|
target,
|
|
162
181
|
kind,
|
|
@@ -164,7 +183,7 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
164
183
|
}
|
|
165
184
|
}
|
|
166
185
|
return {
|
|
167
|
-
...(links && { links
|
|
186
|
+
...(links && { links }),
|
|
168
187
|
...model,
|
|
169
188
|
source,
|
|
170
189
|
target,
|
|
@@ -177,7 +196,9 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
177
196
|
docs,
|
|
178
197
|
flatMap(d => map(d.c4Relations, toModelRelation(d))),
|
|
179
198
|
filter(isTruthy),
|
|
180
|
-
|
|
199
|
+
sort(compareRelations),
|
|
200
|
+
reverse(),
|
|
201
|
+
indexBy(prop('id'))
|
|
181
202
|
)
|
|
182
203
|
|
|
183
204
|
const toC4View = (doc: LangiumDocument) => {
|
|
@@ -188,7 +209,7 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
188
209
|
title,
|
|
189
210
|
description,
|
|
190
211
|
tags,
|
|
191
|
-
links,
|
|
212
|
+
links: unresolvedLinks,
|
|
192
213
|
|
|
193
214
|
// ignore this property
|
|
194
215
|
astPath: _ignore,
|
|
@@ -205,34 +226,32 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
205
226
|
title = 'Landscape view'
|
|
206
227
|
}
|
|
207
228
|
|
|
229
|
+
const links = unresolvedLinks ? resolveLinks(doc, unresolvedLinks) : null
|
|
230
|
+
|
|
208
231
|
return {
|
|
209
|
-
|
|
210
|
-
title,
|
|
211
|
-
description,
|
|
232
|
+
...model,
|
|
212
233
|
tags,
|
|
213
|
-
links
|
|
234
|
+
links,
|
|
214
235
|
docUri,
|
|
215
|
-
|
|
236
|
+
description,
|
|
237
|
+
title,
|
|
238
|
+
id,
|
|
239
|
+
customColorDefinitions
|
|
216
240
|
}
|
|
217
241
|
}
|
|
218
242
|
}
|
|
219
243
|
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
mapToObj(v => [v.id, v]),
|
|
225
|
-
resolveRulesExtendedViews
|
|
226
|
-
)
|
|
227
|
-
// add index view if not present
|
|
228
|
-
if (!('index' in views)) {
|
|
229
|
-
views['index' as ViewID] = {
|
|
244
|
+
const parsedViews = docs.flatMap(d => map(d.c4Views, toC4View(d)))
|
|
245
|
+
// Add index view if not present
|
|
246
|
+
if (!parsedViews.some(v => v.id === 'index')) {
|
|
247
|
+
parsedViews.unshift({
|
|
230
248
|
__: 'element',
|
|
231
249
|
id: 'index' as ViewID,
|
|
232
|
-
title: 'Landscape',
|
|
250
|
+
title: 'Landscape view',
|
|
233
251
|
description: null,
|
|
234
252
|
tags: null,
|
|
235
253
|
links: null,
|
|
254
|
+
customColorDefinitions: customColorDefinitions,
|
|
236
255
|
rules: [
|
|
237
256
|
{
|
|
238
257
|
include: [
|
|
@@ -242,17 +261,30 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
242
261
|
]
|
|
243
262
|
}
|
|
244
263
|
]
|
|
245
|
-
}
|
|
264
|
+
})
|
|
246
265
|
}
|
|
247
266
|
|
|
267
|
+
const views = pipe(
|
|
268
|
+
parsedViews,
|
|
269
|
+
resolveRelativePaths,
|
|
270
|
+
indexBy(prop('id')),
|
|
271
|
+
resolveRulesExtendedViews
|
|
272
|
+
)
|
|
273
|
+
|
|
248
274
|
return {
|
|
275
|
+
specification: {
|
|
276
|
+
tags: Array.from(c4Specification.tags),
|
|
277
|
+
elements: c4Specification.elements,
|
|
278
|
+
relationships: c4Specification.relationships
|
|
279
|
+
},
|
|
249
280
|
elements,
|
|
250
281
|
relations,
|
|
251
282
|
views
|
|
252
283
|
}
|
|
253
284
|
}
|
|
254
|
-
|
|
255
|
-
const
|
|
285
|
+
|
|
286
|
+
const CACHE_KEY_PARSED_MODEL = 'ParsedLikeC4Model'
|
|
287
|
+
const CACHE_KEY_COMPUTED_MODEL = 'ComputedLikeC4Model'
|
|
256
288
|
|
|
257
289
|
type ModelParsedListener = (docs: URI[]) => void
|
|
258
290
|
|
|
@@ -274,9 +306,13 @@ export class LikeC4ModelBuilder {
|
|
|
274
306
|
DocumentState.Validated,
|
|
275
307
|
async (docs, _cancelToken) => {
|
|
276
308
|
let parsed = [] as URI[]
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
309
|
+
try {
|
|
310
|
+
logger.debug(`[ModelBuilder] onValidated (${docs.length} docs)\n${printDocs(docs)}`)
|
|
311
|
+
for (const doc of parser.parse(docs)) {
|
|
312
|
+
parsed.push(doc.uri)
|
|
313
|
+
}
|
|
314
|
+
} catch (e) {
|
|
315
|
+
logWarnError(e)
|
|
280
316
|
}
|
|
281
317
|
if (parsed.length > 0) {
|
|
282
318
|
this.notifyListeners(parsed)
|
|
@@ -287,35 +323,82 @@ export class LikeC4ModelBuilder {
|
|
|
287
323
|
logger.debug(`[ModelBuilder] Created`)
|
|
288
324
|
}
|
|
289
325
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
326
|
+
/**
|
|
327
|
+
* WARNING:
|
|
328
|
+
* This method is internal and should to be called only when all documents are known to be parsed.
|
|
329
|
+
* Otherwise, the model may be incomplete.
|
|
330
|
+
*/
|
|
331
|
+
public unsafeSyncBuildModel(): c4.ParsedLikeC4Model | null {
|
|
332
|
+
const cache = this.services.WorkspaceCache as WorkspaceCache<string, c4.ParsedLikeC4Model | null>
|
|
333
|
+
return cache.get(CACHE_KEY_PARSED_MODEL, () => {
|
|
334
|
+
const docs = this.documents()
|
|
335
|
+
if (docs.length === 0) {
|
|
336
|
+
logger.debug('[ModelBuilder] No documents to build model from')
|
|
337
|
+
return null
|
|
338
|
+
}
|
|
339
|
+
logger.debug(`[ModelBuilder] buildModel from ${docs.length} docs:\n${printDocs(docs)}`)
|
|
340
|
+
return buildModel(this.services, docs)
|
|
341
|
+
})
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
public async buildModel(cancelToken?: Cancellation.CancellationToken): Promise<c4.ParsedLikeC4Model | null> {
|
|
345
|
+
const cache = this.services.WorkspaceCache as WorkspaceCache<string, c4.ParsedLikeC4Model | null>
|
|
346
|
+
if (cache.has(CACHE_KEY_PARSED_MODEL)) {
|
|
347
|
+
return cache.get(CACHE_KEY_PARSED_MODEL)!
|
|
294
348
|
}
|
|
295
349
|
return await this.services.shared.workspace.WorkspaceLock.read(async () => {
|
|
296
350
|
if (cancelToken) {
|
|
297
351
|
await interruptAndCheck(cancelToken)
|
|
298
352
|
}
|
|
299
|
-
return
|
|
300
|
-
const docs = this.documents()
|
|
301
|
-
if (docs.length === 0) {
|
|
302
|
-
logger.debug('[ModelBuilder] No documents to build model from')
|
|
303
|
-
return null
|
|
304
|
-
}
|
|
305
|
-
logger.debug(`[ModelBuilder] buildModel from ${docs.length} docs:\n${printDocs(docs)}`)
|
|
306
|
-
return buildModel(this.services, docs)
|
|
307
|
-
})
|
|
353
|
+
return this.unsafeSyncBuildModel()
|
|
308
354
|
})
|
|
309
355
|
}
|
|
310
356
|
|
|
311
357
|
private previousViews: Record<ViewID, c4.ComputedView> = {}
|
|
312
358
|
|
|
359
|
+
/**
|
|
360
|
+
* WARNING:
|
|
361
|
+
* This method is internal and should to be called only when all documents are known to be parsed.
|
|
362
|
+
* Otherwise, the model may be incomplete.
|
|
363
|
+
*/
|
|
364
|
+
public unsafeSyncBuildComputedModel(model: c4.ParsedLikeC4Model): c4.ComputedLikeC4Model {
|
|
365
|
+
const cache = this.services.WorkspaceCache as WorkspaceCache<string, c4.ComputedLikeC4Model>
|
|
366
|
+
const viewsCache = this.services.WorkspaceCache as WorkspaceCache<string, c4.ComputedView | null>
|
|
367
|
+
return cache.get(CACHE_KEY_COMPUTED_MODEL, () => {
|
|
368
|
+
const index = new LikeC4ModelGraph(model)
|
|
369
|
+
|
|
370
|
+
const allViews = [] as c4.ComputedView[]
|
|
371
|
+
for (const view of values(model.views)) {
|
|
372
|
+
const result = isElementView(view) ? computeView(view, index) : computeDynamicView(view, index)
|
|
373
|
+
if (!result.isSuccess) {
|
|
374
|
+
logWarnError(result.error)
|
|
375
|
+
continue
|
|
376
|
+
}
|
|
377
|
+
allViews.push(result.view)
|
|
378
|
+
}
|
|
379
|
+
assignNavigateTo(allViews)
|
|
380
|
+
const views = mapToObj(allViews, v => {
|
|
381
|
+
const previous = this.previousViews[v.id]
|
|
382
|
+
const view = previous && eq(v, previous) ? previous : v
|
|
383
|
+
viewsCache.set(computedViewKey(v.id), view)
|
|
384
|
+
return [v.id, view] as const
|
|
385
|
+
})
|
|
386
|
+
this.previousViews = { ...views }
|
|
387
|
+
return {
|
|
388
|
+
specification: model.specification,
|
|
389
|
+
elements: model.elements,
|
|
390
|
+
relations: model.relations,
|
|
391
|
+
views
|
|
392
|
+
}
|
|
393
|
+
})
|
|
394
|
+
}
|
|
395
|
+
|
|
313
396
|
public async buildComputedModel(
|
|
314
397
|
cancelToken?: Cancellation.CancellationToken
|
|
315
|
-
): Promise<c4.
|
|
316
|
-
const cache = this.services.WorkspaceCache as WorkspaceCache<string, c4.
|
|
317
|
-
if (cache.has(
|
|
318
|
-
return cache.get(
|
|
398
|
+
): Promise<c4.ComputedLikeC4Model | null> {
|
|
399
|
+
const cache = this.services.WorkspaceCache as WorkspaceCache<string, c4.ComputedLikeC4Model | null>
|
|
400
|
+
if (cache.has(CACHE_KEY_COMPUTED_MODEL)) {
|
|
401
|
+
return cache.get(CACHE_KEY_COMPUTED_MODEL)!
|
|
319
402
|
}
|
|
320
403
|
const model = await this.buildModel(cancelToken)
|
|
321
404
|
if (!model) {
|
|
@@ -325,33 +408,7 @@ export class LikeC4ModelBuilder {
|
|
|
325
408
|
if (cancelToken) {
|
|
326
409
|
await interruptAndCheck(cancelToken)
|
|
327
410
|
}
|
|
328
|
-
|
|
329
|
-
return cache.get(MODEL_CACHE, () => {
|
|
330
|
-
const index = new LikeC4ModelGraph(model)
|
|
331
|
-
|
|
332
|
-
const allViews = [] as c4.ComputedView[]
|
|
333
|
-
for (const view of values(model.views)) {
|
|
334
|
-
const result = isElementView(view) ? computeView(view, index) : computeDynamicView(view, index)
|
|
335
|
-
if (!result.isSuccess) {
|
|
336
|
-
logWarnError(result.error)
|
|
337
|
-
continue
|
|
338
|
-
}
|
|
339
|
-
allViews.push(result.view)
|
|
340
|
-
}
|
|
341
|
-
assignNavigateTo(allViews)
|
|
342
|
-
const views = mapToObj(allViews, v => {
|
|
343
|
-
const previous = this.previousViews[v.id]
|
|
344
|
-
const view = previous && eq(v, previous) ? previous : v
|
|
345
|
-
viewsCache.set(computedViewKey(v.id), view)
|
|
346
|
-
return [v.id, view] as const
|
|
347
|
-
})
|
|
348
|
-
this.previousViews = { ...views }
|
|
349
|
-
return {
|
|
350
|
-
elements: model.elements,
|
|
351
|
-
relations: model.relations,
|
|
352
|
-
views
|
|
353
|
-
}
|
|
354
|
-
})
|
|
411
|
+
return this.unsafeSyncBuildComputedModel(model)
|
|
355
412
|
})
|
|
356
413
|
}
|
|
357
414
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { isAndOperator, isOrOperator } from '@likec4/core
|
|
1
|
+
import { invariant, isNonEmptyArray, nonexhaustive } from '@likec4/core'
|
|
2
|
+
import { isAndOperator, isOrOperator } from '@likec4/core'
|
|
3
|
+
import type * as c4 from '@likec4/core'
|
|
3
4
|
import { ast } from '../ast'
|
|
4
5
|
|
|
5
6
|
const parseEquals = (
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type HexColorLiteral, invariant, isNonEmptyArray, nonexhaustive } from '@likec4/core'
|
|
2
|
+
import type * as c4 from '@likec4/core'
|
|
2
3
|
import type { AstNode, LangiumDocument } from 'langium'
|
|
3
4
|
import { AstUtils, CstUtils } from 'langium'
|
|
4
5
|
import { filter, flatMap, isDefined, isNonNullish, isTruthy, mapToObj, pipe } from 'remeda'
|
|
@@ -23,6 +24,7 @@ import {
|
|
|
23
24
|
resolveRelationPoints,
|
|
24
25
|
streamModel,
|
|
25
26
|
toAutoLayout,
|
|
27
|
+
toColor,
|
|
26
28
|
toElementStyle,
|
|
27
29
|
toRelationshipStyleExcludeDefaults,
|
|
28
30
|
ViewOps
|
|
@@ -67,7 +69,7 @@ export class LikeC4ModelParser {
|
|
|
67
69
|
try {
|
|
68
70
|
result.push(this.parseLikeC4Document(doc))
|
|
69
71
|
} catch (cause) {
|
|
70
|
-
logError(new
|
|
72
|
+
logError(new Error(`Error parsing document ${doc.uri.toString()}`, { cause }))
|
|
71
73
|
}
|
|
72
74
|
}
|
|
73
75
|
return result
|
|
@@ -89,20 +91,23 @@ export class LikeC4ModelParser {
|
|
|
89
91
|
const element_specs = specifications.flatMap(s => s.elements.filter(isValid))
|
|
90
92
|
for (const { kind, props } of element_specs) {
|
|
91
93
|
try {
|
|
92
|
-
const style = props.find(ast.isElementStyleProperty)
|
|
93
94
|
const kindName = kind.name as c4.ElementKind
|
|
94
|
-
if (kindName
|
|
95
|
+
if (!isTruthy(kindName)) {
|
|
96
|
+
continue
|
|
97
|
+
}
|
|
98
|
+
if (kindName in c4Specification.elements) {
|
|
95
99
|
logger.warn(`Element kind "${kindName}" is already defined`)
|
|
96
100
|
continue
|
|
97
101
|
}
|
|
102
|
+
const style = props.find(ast.isElementStyleProperty)
|
|
98
103
|
const bodyProps = mapToObj(
|
|
99
104
|
props.filter(ast.isSpecificationElementStringProperty).filter(p => isNonNullish(p.value)) ?? [],
|
|
100
|
-
p => [p.key, removeIndent(p.value)]
|
|
105
|
+
p => [p.key, removeIndent(p.value)] as const
|
|
101
106
|
)
|
|
102
|
-
c4Specification.
|
|
107
|
+
c4Specification.elements[kindName] = {
|
|
103
108
|
...bodyProps,
|
|
104
109
|
style: {
|
|
105
|
-
...toElementStyle(style?.props)
|
|
110
|
+
...toElementStyle(style?.props, isValid)
|
|
106
111
|
}
|
|
107
112
|
}
|
|
108
113
|
} catch (e) {
|
|
@@ -114,13 +119,16 @@ export class LikeC4ModelParser {
|
|
|
114
119
|
for (const { kind, props } of relations_specs) {
|
|
115
120
|
try {
|
|
116
121
|
const kindName = kind.name as c4.RelationshipKind
|
|
122
|
+
if (!isTruthy(kindName)) {
|
|
123
|
+
continue
|
|
124
|
+
}
|
|
117
125
|
if (kindName in c4Specification.relationships) {
|
|
118
126
|
logger.warn(`Relationship kind "${kindName}" is already defined`)
|
|
119
127
|
continue
|
|
120
128
|
}
|
|
121
129
|
const bodyProps = mapToObj(
|
|
122
130
|
props.filter(ast.isSpecificationRelationshipStringProperty).filter(p => isNonNullish(p.value)) ?? [],
|
|
123
|
-
p => [p.key, p.value]
|
|
131
|
+
p => [p.key, removeIndent(p.value)]
|
|
124
132
|
)
|
|
125
133
|
c4Specification.relationships[kindName] = {
|
|
126
134
|
...bodyProps,
|
|
@@ -130,13 +138,38 @@ export class LikeC4ModelParser {
|
|
|
130
138
|
logWarnError(e)
|
|
131
139
|
}
|
|
132
140
|
}
|
|
141
|
+
|
|
142
|
+
const tags_specs = specifications.flatMap(s => s.tags.filter(isValid))
|
|
143
|
+
for (const tagSpec of tags_specs) {
|
|
144
|
+
const tag = tagSpec.tag.name as c4.Tag
|
|
145
|
+
if (isTruthy(tag)) {
|
|
146
|
+
c4Specification.tags.add(tag)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const colors_specs = specifications.flatMap(s => s.colors.filter(isValid))
|
|
151
|
+
for (const { name, color } of colors_specs) {
|
|
152
|
+
try {
|
|
153
|
+
const colorName = name.name as c4.CustomColor
|
|
154
|
+
if (colorName in c4Specification.colors) {
|
|
155
|
+
logger.warn(`Custom color "${colorName}" is already defined`)
|
|
156
|
+
continue
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
c4Specification.colors[colorName] = {
|
|
160
|
+
color: color as HexColorLiteral
|
|
161
|
+
}
|
|
162
|
+
} catch (e) {
|
|
163
|
+
logWarnError(e)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
133
166
|
}
|
|
134
167
|
|
|
135
168
|
private parseModel(doc: ParsedLikeC4LangiumDocument, isValid: IsValidFn) {
|
|
136
169
|
for (const el of streamModel(doc, isValid)) {
|
|
137
170
|
if (ast.isElement(el)) {
|
|
138
171
|
try {
|
|
139
|
-
doc.c4Elements.push(this.parseElement(el))
|
|
172
|
+
doc.c4Elements.push(this.parseElement(el, isValid))
|
|
140
173
|
} catch (e) {
|
|
141
174
|
logWarnError(e)
|
|
142
175
|
}
|
|
@@ -154,12 +187,12 @@ export class LikeC4ModelParser {
|
|
|
154
187
|
}
|
|
155
188
|
}
|
|
156
189
|
|
|
157
|
-
private parseElement(astNode: ast.Element): ParsedAstElement {
|
|
190
|
+
private parseElement(astNode: ast.Element, isValid: IsValidFn): ParsedAstElement {
|
|
158
191
|
const id = this.resolveFqn(astNode)
|
|
159
192
|
const kind = astNode.kind.$refText as c4.ElementKind
|
|
160
193
|
const tags = this.convertTags(astNode.body)
|
|
161
194
|
const stylePropsAst = astNode.body?.props.find(ast.isElementStyleProperty)?.props
|
|
162
|
-
const style = toElementStyle(stylePropsAst)
|
|
195
|
+
const style = toElementStyle(stylePropsAst, isValid)
|
|
163
196
|
const metadata = this.getMetadata(astNode.body?.props.find(ast.isMetadataProperty))
|
|
164
197
|
const astPath = this.getAstNodePath(astNode)
|
|
165
198
|
|
|
@@ -392,8 +425,9 @@ export class LikeC4ModelParser {
|
|
|
392
425
|
return acc
|
|
393
426
|
}
|
|
394
427
|
if (ast.isColorProperty(prop)) {
|
|
395
|
-
|
|
396
|
-
|
|
428
|
+
const value = toColor(prop)
|
|
429
|
+
if (isDefined(value)) {
|
|
430
|
+
acc.custom[prop.key] = value
|
|
397
431
|
}
|
|
398
432
|
return acc
|
|
399
433
|
}
|
|
@@ -493,8 +527,9 @@ export class LikeC4ModelParser {
|
|
|
493
527
|
return acc
|
|
494
528
|
}
|
|
495
529
|
if (ast.isColorProperty(prop)) {
|
|
496
|
-
|
|
497
|
-
|
|
530
|
+
const value = toColor(prop)
|
|
531
|
+
if (isTruthy(value)) {
|
|
532
|
+
acc.customRelation[prop.key] = value
|
|
498
533
|
}
|
|
499
534
|
return acc
|
|
500
535
|
}
|
|
@@ -551,7 +586,7 @@ export class LikeC4ModelParser {
|
|
|
551
586
|
return this.parseViewRulePredicate(astRule, isValid)
|
|
552
587
|
}
|
|
553
588
|
if (ast.isViewRuleStyle(astRule)) {
|
|
554
|
-
const styleProps = toElementStyle(astRule.props.filter(ast.isStyleProperty))
|
|
589
|
+
const styleProps = toElementStyle(astRule.props.filter(ast.isStyleProperty), isValid)
|
|
555
590
|
const notation = removeIndent(astRule.props.find(ast.isNotationProperty)?.value)
|
|
556
591
|
const targets = this.parseElementExpressionsIterator(astRule.target)
|
|
557
592
|
return {
|
|
@@ -632,7 +667,10 @@ export class LikeC4ModelParser {
|
|
|
632
667
|
continue
|
|
633
668
|
}
|
|
634
669
|
if (ast.isColorProperty(prop)) {
|
|
635
|
-
|
|
670
|
+
const value = toColor(prop)
|
|
671
|
+
if (isTruthy(value)) {
|
|
672
|
+
step[prop.key] = value
|
|
673
|
+
}
|
|
636
674
|
continue
|
|
637
675
|
}
|
|
638
676
|
if (ast.isLineProperty(prop)) {
|
|
@@ -778,7 +816,7 @@ export class LikeC4ModelParser {
|
|
|
778
816
|
return acc
|
|
779
817
|
}
|
|
780
818
|
if (ast.isViewRuleStyle(n)) {
|
|
781
|
-
const styleProps = toElementStyle(n.props.filter(ast.isStyleProperty))
|
|
819
|
+
const styleProps = toElementStyle(n.props.filter(ast.isStyleProperty), isValid)
|
|
782
820
|
const notation = removeIndent(n.props.find(ast.isNotationProperty)?.value)
|
|
783
821
|
const targets = this.parseElementExpressionsIterator(n.target)
|
|
784
822
|
if (targets.length > 0) {
|
|
@@ -845,7 +883,7 @@ export class LikeC4ModelParser {
|
|
|
845
883
|
const tags = [] as c4.Tag[]
|
|
846
884
|
while (iter) {
|
|
847
885
|
try {
|
|
848
|
-
const values = iter.values.map(t => t.ref?.name).filter(
|
|
886
|
+
const values = iter.values.map(t => t.ref?.name).filter(isTruthy) as c4.Tag[]
|
|
849
887
|
if (values.length > 0) {
|
|
850
888
|
tags.unshift(...values)
|
|
851
889
|
}
|