@likec4/language-server 1.17.1 → 1.18.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/protocol.d.cts +8 -5
- package/dist/protocol.d.mts +8 -5
- package/dist/protocol.d.ts +8 -5
- package/dist/shared/{language-server.DKV_FdPN.cjs → language-server.CO_nmHiL.cjs} +5598 -4213
- package/dist/shared/{language-server.BQRvVmE0.d.cts → language-server.Da6ey08o.d.cts} +390 -74
- package/dist/shared/{language-server.BysPcTxr.d.ts → language-server.De7S3e5Z.d.ts} +390 -74
- package/dist/shared/{language-server._wkyPgso.d.mts → language-server.Dj4iDjtB.d.mts} +390 -74
- package/dist/shared/{language-server.BIbAD1T-.mjs → language-server.oO_9JoAG.mjs} +5604 -4230
- package/package.json +11 -25
- package/src/Rpc.ts +6 -3
- package/src/ast.ts +124 -71
- package/src/generated/ast.ts +655 -39
- package/src/generated/grammar.ts +1 -1
- package/src/index.ts +1 -0
- package/src/like-c4.langium +170 -22
- package/src/logger.ts +7 -2
- package/src/lsp/CodeLensProvider.ts +0 -1
- package/src/lsp/CompletionProvider.ts +17 -2
- package/src/lsp/DocumentSymbolProvider.ts +5 -2
- package/src/lsp/HoverProvider.ts +34 -2
- package/src/lsp/SemanticTokenProvider.ts +58 -32
- package/src/model/deployments-index.ts +218 -0
- package/src/model/fqn-computation.ts +1 -1
- package/src/model/fqn-index.ts +0 -1
- package/src/model/index.ts +1 -0
- package/src/model/model-builder.ts +172 -37
- package/src/model/model-locator.ts +36 -7
- package/src/model/model-parser.ts +554 -92
- package/src/model-change/changeViewLayout.ts +2 -2
- package/src/module.ts +10 -4
- package/src/protocol.ts +10 -6
- package/src/references/index.ts +1 -0
- package/src/references/name-provider.ts +37 -0
- package/src/references/scope-computation.ts +130 -21
- package/src/references/scope-provider.ts +63 -36
- package/src/shared/NodeKindProvider.ts +15 -3
- package/src/utils/deploymentRef.ts +31 -0
- package/src/{elementRef.ts → utils/elementRef.ts} +1 -1
- package/src/utils/stringHash.ts +2 -2
- package/src/validation/_shared.ts +7 -5
- package/src/validation/deployment-checks.ts +144 -0
- package/src/validation/dynamic-view-step.ts +1 -1
- package/src/validation/index.ts +7 -0
- package/src/validation/relation.ts +1 -1
- package/src/validation/view-predicates/deployments.ts +56 -0
- package/src/validation/view-predicates/index.ts +1 -0
- package/src/view-utils/assignNavigateTo.ts +6 -5
- package/src/view-utils/index.ts +0 -1
- package/dist/model-graph/index.cjs +0 -10
- package/dist/model-graph/index.d.cts +0 -81
- package/dist/model-graph/index.d.mts +0 -81
- package/dist/model-graph/index.d.ts +0 -81
- package/dist/model-graph/index.mjs +0 -1
- package/dist/shared/language-server.D2QdbOJO.cjs +0 -1995
- package/dist/shared/language-server.DGrBGmsd.mjs +0 -1981
- package/src/model-graph/LikeC4ModelGraph.ts +0 -338
- package/src/model-graph/compute-view/__test__/fixture.ts +0 -630
- package/src/model-graph/compute-view/compute.ts +0 -788
- package/src/model-graph/compute-view/index.ts +0 -33
- package/src/model-graph/compute-view/predicates.ts +0 -509
- package/src/model-graph/dynamic-view/__test__/fixture.ts +0 -61
- package/src/model-graph/dynamic-view/compute.ts +0 -313
- package/src/model-graph/dynamic-view/index.ts +0 -29
- package/src/model-graph/index.ts +0 -3
- package/src/model-graph/utils/applyCustomElementProperties.ts +0 -65
- package/src/model-graph/utils/applyCustomRelationProperties.ts +0 -41
- package/src/model-graph/utils/applyViewRuleStyles.ts +0 -49
- package/src/model-graph/utils/buildComputeNodes.ts +0 -113
- package/src/model-graph/utils/buildElementNotations.ts +0 -63
- package/src/model-graph/utils/elementExpressionToPredicate.ts +0 -39
- package/src/model-graph/utils/relationExpressionToPredicates.ts +0 -43
- package/src/model-graph/utils/sortNodes.ts +0 -105
- package/src/model-graph/utils/uniqueTags.test.ts +0 -42
- package/src/model-graph/utils/uniqueTags.ts +0 -19
- package/src/utils/graphlib.ts +0 -9
- package/src/view-utils/resolve-extended-views.ts +0 -66
- package/src/view-utils/resolve-global-rules.ts +0 -88
- package/src/view-utils/view-hash.ts +0 -27
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import type { Fqn } from '@likec4/core'
|
|
2
|
+
import type { LangiumDocument, LangiumDocuments, Stream } from 'langium'
|
|
3
|
+
import { AstUtils, DocumentState, MultiMap } from 'langium'
|
|
4
|
+
import { forEachObj, groupBy, isTruthy, pipe, prop } from 'remeda'
|
|
5
|
+
import {
|
|
6
|
+
ast,
|
|
7
|
+
type DeploymentAstNodeDescription,
|
|
8
|
+
ElementOps,
|
|
9
|
+
isLikeC4LangiumDocument,
|
|
10
|
+
type LikeC4LangiumDocument
|
|
11
|
+
} from '../ast'
|
|
12
|
+
import { logWarnError } from '../logger'
|
|
13
|
+
import type { LikeC4Services } from '../module'
|
|
14
|
+
import type { LikeC4NameProvider } from '../references'
|
|
15
|
+
|
|
16
|
+
const DeploymentsIndexKey = Symbol.for('DeploymentsIndex')
|
|
17
|
+
|
|
18
|
+
type IndexedDocument = LangiumDocument & {
|
|
19
|
+
[DeploymentsIndexKey]?: DocumentDeploymentsIndex
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class DeploymentsIndex {
|
|
23
|
+
protected Names: LikeC4NameProvider
|
|
24
|
+
protected langiumDocuments: LangiumDocuments
|
|
25
|
+
|
|
26
|
+
constructor(private services: LikeC4Services) {
|
|
27
|
+
this.Names = services.references.NameProvider
|
|
28
|
+
this.langiumDocuments = services.shared.workspace.LangiumDocuments
|
|
29
|
+
|
|
30
|
+
services.shared.workspace.DocumentBuilder.onBuildPhase(
|
|
31
|
+
DocumentState.IndexedContent,
|
|
32
|
+
(docs, _cancelToken) => {
|
|
33
|
+
for (const doc of docs) {
|
|
34
|
+
delete (doc as IndexedDocument)[DeploymentsIndexKey]
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private documents() {
|
|
41
|
+
return this.langiumDocuments.all.filter((d): d is LikeC4LangiumDocument =>
|
|
42
|
+
isLikeC4LangiumDocument(d) && d.state >= DocumentState.IndexedContent
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public get(document: LikeC4LangiumDocument): DocumentDeploymentsIndex {
|
|
47
|
+
if (document.state < DocumentState.IndexedContent) {
|
|
48
|
+
logWarnError(`Document ${document.uri.path} is not indexed`)
|
|
49
|
+
}
|
|
50
|
+
return (document as IndexedDocument)[DeploymentsIndexKey] ??= this.createDocumentIndex(document)
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Nested elements (nodes/artifacts) of the node
|
|
54
|
+
* @param nodeName Name of the deployment node
|
|
55
|
+
* @returns Stream of artifacts
|
|
56
|
+
*/
|
|
57
|
+
public nested(node: ast.DeploymentNode): Stream<DeploymentAstNodeDescription> {
|
|
58
|
+
const fqnName = this.getFqnName(node)
|
|
59
|
+
return this.documents().flatMap(doc => this.get(doc).nested(fqnName))
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public byFqn(fqnName: string): Stream<DeploymentAstNodeDescription> {
|
|
63
|
+
return this.documents().flatMap(doc => this.get(doc).byFqn(fqnName))
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public getFqnName(node: ast.DeploymentElement): Fqn {
|
|
67
|
+
let id = ElementOps.readId(node)
|
|
68
|
+
if (isTruthy(id)) {
|
|
69
|
+
return id
|
|
70
|
+
}
|
|
71
|
+
const fqn = [
|
|
72
|
+
this.Names.getNameStrict(node)
|
|
73
|
+
]
|
|
74
|
+
let parentNode: ast.DeploymentNode | undefined
|
|
75
|
+
while ((parentNode = AstUtils.getContainerOfType(node.$container, ast.isDeploymentNode))) {
|
|
76
|
+
fqn.push(this.Names.getNameStrict(parentNode))
|
|
77
|
+
node = parentNode
|
|
78
|
+
}
|
|
79
|
+
id = fqn.reduceRight((acc, cur) => `${acc}.${cur}`) as Fqn
|
|
80
|
+
ElementOps.writeId(node, id)
|
|
81
|
+
return id
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
public createDocumentIndex(document: LikeC4LangiumDocument): DocumentDeploymentsIndex {
|
|
85
|
+
const rootNodes = document.parseResult.value.deployments.flatMap(m => m.elements)
|
|
86
|
+
if (rootNodes.length === 0) {
|
|
87
|
+
return DocumentDeploymentsIndex.EMPTY
|
|
88
|
+
}
|
|
89
|
+
const _root = new Array<DeploymentAstNodeDescription>()
|
|
90
|
+
const _nested = new MultiMap<string, DeploymentAstNodeDescription>()
|
|
91
|
+
const _byfqn = new MultiMap<string, DeploymentAstNodeDescription>()
|
|
92
|
+
const Names = this.Names
|
|
93
|
+
const Descriptions = this.services.workspace.AstNodeDescriptionProvider
|
|
94
|
+
|
|
95
|
+
const createAndSaveDescription = (
|
|
96
|
+
props: { node: ast.DeploymentNode | ast.DeployedInstance; name: string; fqn: string }
|
|
97
|
+
) => {
|
|
98
|
+
const desc = {
|
|
99
|
+
...Descriptions.createDescription(props.node, props.name, document),
|
|
100
|
+
fqn: props.fqn
|
|
101
|
+
}
|
|
102
|
+
ElementOps.writeId(props.node, props.fqn as Fqn)
|
|
103
|
+
_byfqn.add(props.fqn, desc)
|
|
104
|
+
return desc
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const traverseNode = (
|
|
108
|
+
container: ast.DeploymentNode,
|
|
109
|
+
parentFqn: string
|
|
110
|
+
): readonly DeploymentAstNodeDescription[] => {
|
|
111
|
+
const _descedants = [] as DeploymentAstNodeDescription[]
|
|
112
|
+
const children = container.body?.elements
|
|
113
|
+
if (!children || children.length === 0) {
|
|
114
|
+
return []
|
|
115
|
+
}
|
|
116
|
+
const directChildren = new Set<string>()
|
|
117
|
+
for (const node of children) {
|
|
118
|
+
if (ast.isDeploymentRelation(node)) {
|
|
119
|
+
continue
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
const name = Names.getName(node)
|
|
123
|
+
if (isTruthy(name)) {
|
|
124
|
+
const fqn = `${parentFqn}.${name}`
|
|
125
|
+
const desc = createAndSaveDescription({ node, name, fqn })
|
|
126
|
+
_nested.add(parentFqn, desc)
|
|
127
|
+
directChildren.add(desc.name)
|
|
128
|
+
if (ast.isDeploymentNode(node) && node.body) {
|
|
129
|
+
_descedants.push(...traverseNode(node, fqn))
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
} catch (e) {
|
|
133
|
+
logWarnError(e)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (_descedants.length > 0) {
|
|
137
|
+
pipe(
|
|
138
|
+
_descedants,
|
|
139
|
+
groupBy(prop('name')),
|
|
140
|
+
forEachObj((descs, key) => {
|
|
141
|
+
if (descs.length > 1 || directChildren.has(key)) {
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
_nested.add(parentFqn, descs[0])
|
|
145
|
+
})
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
return _nested.get(parentFqn)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
for (const node of rootNodes) {
|
|
152
|
+
try {
|
|
153
|
+
if (ast.isDeploymentRelation(node)) {
|
|
154
|
+
continue
|
|
155
|
+
}
|
|
156
|
+
const name = Names.getName(node)
|
|
157
|
+
if (isTruthy(name)) {
|
|
158
|
+
_root.push(createAndSaveDescription({ node, name, fqn: name }))
|
|
159
|
+
traverseNode(node, name)
|
|
160
|
+
}
|
|
161
|
+
} catch (e) {
|
|
162
|
+
logWarnError(e)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return new DocumentDeploymentsIndex(_root, _nested, _byfqn)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export class DocumentDeploymentsIndex {
|
|
170
|
+
static readonly EMPTY = new DocumentDeploymentsIndex([], new MultiMap(), new MultiMap())
|
|
171
|
+
|
|
172
|
+
constructor(
|
|
173
|
+
private _rootNodes: Array<DeploymentAstNodeDescription>,
|
|
174
|
+
/**
|
|
175
|
+
* Nested of a deployment node
|
|
176
|
+
*/
|
|
177
|
+
private _nested: MultiMap<string, DeploymentAstNodeDescription>,
|
|
178
|
+
/**
|
|
179
|
+
* All elements by FQN
|
|
180
|
+
*/
|
|
181
|
+
private _byfqn: MultiMap<string, DeploymentAstNodeDescription>
|
|
182
|
+
) {}
|
|
183
|
+
|
|
184
|
+
public rootNodes(): readonly DeploymentAstNodeDescription[] {
|
|
185
|
+
return this._rootNodes
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
public byFqn(fqnName: string): readonly DeploymentAstNodeDescription[] {
|
|
189
|
+
return this._byfqn.get(fqnName)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Returns artifacts of a deployment node
|
|
194
|
+
* @param nodeName Name of the deployment node
|
|
195
|
+
* @returns Stream of artifacts
|
|
196
|
+
*/
|
|
197
|
+
public nested(nodeName: string): readonly DeploymentAstNodeDescription[] {
|
|
198
|
+
return this._nested.get(nodeName)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Returns all deployment elements in the document,
|
|
203
|
+
* with unique combination "type and name"
|
|
204
|
+
*/
|
|
205
|
+
public unique(): readonly DeploymentAstNodeDescription[] {
|
|
206
|
+
const result = [] as DeploymentAstNodeDescription[]
|
|
207
|
+
pipe(
|
|
208
|
+
[...this._byfqn.values()],
|
|
209
|
+
groupBy(prop('name')),
|
|
210
|
+
forEachObj(descs => {
|
|
211
|
+
if (descs.length === 1) {
|
|
212
|
+
result.push(descs[0])
|
|
213
|
+
}
|
|
214
|
+
})
|
|
215
|
+
)
|
|
216
|
+
return result
|
|
217
|
+
}
|
|
218
|
+
}
|
|
@@ -3,9 +3,9 @@ import type * as c4 from '@likec4/core'
|
|
|
3
3
|
import { type AstNodeDescription, type AstNodeLocator, AstUtils, CstUtils, GrammarUtils, MultiMap } from 'langium'
|
|
4
4
|
import { isDefined, isEmpty } from 'remeda'
|
|
5
5
|
import { ast, ElementOps, type LikeC4LangiumDocument } from '../ast'
|
|
6
|
-
import { getFqnElementRef } from '../elementRef'
|
|
7
6
|
import { logError } from '../logger'
|
|
8
7
|
import type { LikeC4Services } from '../module'
|
|
8
|
+
import { getFqnElementRef } from '../utils/elementRef'
|
|
9
9
|
|
|
10
10
|
const { findNodeForProperty } = GrammarUtils
|
|
11
11
|
const { toDocumentSegment } = CstUtils
|
package/src/model/fqn-index.ts
CHANGED
|
@@ -6,7 +6,6 @@ import type { ast, DocFqnIndexAstNodeDescription, FqnIndexedDocument } from '../
|
|
|
6
6
|
import { ElementOps, isFqnIndexedDocument, isLikeC4LangiumDocument } from '../ast'
|
|
7
7
|
import { logger, logWarnError } from '../logger'
|
|
8
8
|
import type { LikeC4Services } from '../module'
|
|
9
|
-
import { printDocs } from '../utils/printDocs'
|
|
10
9
|
import { computeDocumentFqn } from './fqn-computation'
|
|
11
10
|
|
|
12
11
|
export interface FqnIndexEntry {
|
package/src/model/index.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as c4 from '@likec4/core'
|
|
2
2
|
import {
|
|
3
3
|
compareRelations,
|
|
4
4
|
computeColorValues,
|
|
5
5
|
type CustomColorDefinitions,
|
|
6
|
-
isElementView,
|
|
7
6
|
isScopedElementView,
|
|
8
7
|
parentFqn,
|
|
9
8
|
sortByFqnHierarchically,
|
|
10
|
-
type
|
|
9
|
+
type ViewId
|
|
11
10
|
} from '@likec4/core'
|
|
11
|
+
import { mkComputeView, resolveRulesExtendedViews } from '@likec4/core/compute-view'
|
|
12
12
|
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'
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
flatMap,
|
|
18
18
|
groupBy,
|
|
19
19
|
indexBy,
|
|
20
|
+
isDefined,
|
|
20
21
|
isEmpty,
|
|
21
22
|
isNonNullish,
|
|
22
23
|
isNullish,
|
|
@@ -25,6 +26,7 @@ import {
|
|
|
25
26
|
map,
|
|
26
27
|
mapToObj,
|
|
27
28
|
mapValues,
|
|
29
|
+
pick,
|
|
28
30
|
pipe,
|
|
29
31
|
prop,
|
|
30
32
|
reduce,
|
|
@@ -33,6 +35,7 @@ import {
|
|
|
33
35
|
values
|
|
34
36
|
} from 'remeda'
|
|
35
37
|
import type {
|
|
38
|
+
ParsedAstDeploymentRelation,
|
|
36
39
|
ParsedAstElement,
|
|
37
40
|
ParsedAstRelation,
|
|
38
41
|
ParsedAstSpecification,
|
|
@@ -42,14 +45,14 @@ import type {
|
|
|
42
45
|
} from '../ast'
|
|
43
46
|
import { isParsedLikeC4LangiumDocument } from '../ast'
|
|
44
47
|
import { logError, logger, logWarnError } from '../logger'
|
|
45
|
-
import { computeDynamicView, computeView, LikeC4ModelGraph } from '../model-graph'
|
|
46
48
|
import type { LikeC4Services } from '../module'
|
|
47
|
-
import { assignNavigateTo, resolveRelativePaths
|
|
49
|
+
import { assignNavigateTo, resolveRelativePaths } from '../view-utils'
|
|
48
50
|
|
|
49
51
|
function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[]): c4.ParsedLikeC4Model {
|
|
50
52
|
// Merge specifications and globals from all documents
|
|
51
53
|
const c4Specification: ParsedAstSpecification = {
|
|
52
54
|
tags: new Set(),
|
|
55
|
+
deployments: {},
|
|
53
56
|
elements: {},
|
|
54
57
|
relationships: {},
|
|
55
58
|
colors: {}
|
|
@@ -69,7 +72,7 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
69
72
|
Object.assign(c4Specification.elements, spec.elements)
|
|
70
73
|
Object.assign(c4Specification.relationships, spec.relationships)
|
|
71
74
|
Object.assign(c4Specification.colors, spec.colors)
|
|
72
|
-
|
|
75
|
+
Object.assign(c4Specification.deployments, spec.deployments)
|
|
73
76
|
Object.assign(globals.predicates, c4Globals.predicates)
|
|
74
77
|
Object.assign(globals.dynamicPredicates, c4Globals.dynamicPredicates)
|
|
75
78
|
Object.assign(globals.styles, c4Globals.styles)
|
|
@@ -189,7 +192,9 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
189
192
|
}: ParsedAstRelation): c4.Relation | null => {
|
|
190
193
|
if (isNullish(elements[source]) || isNullish(elements[target])) {
|
|
191
194
|
logger.warn(
|
|
192
|
-
`Invalid relation ${id}
|
|
195
|
+
`Invalid relation ${id} at ${doc.uri.path} ${astPath}, source: ${source}(${!!elements[
|
|
196
|
+
source
|
|
197
|
+
]}), target: ${target}(${!!elements[target]})`
|
|
193
198
|
)
|
|
194
199
|
return null
|
|
195
200
|
}
|
|
@@ -207,7 +212,7 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
207
212
|
} satisfies c4.Relation
|
|
208
213
|
}
|
|
209
214
|
return {
|
|
210
|
-
...
|
|
215
|
+
...links && { links },
|
|
211
216
|
...model,
|
|
212
217
|
source,
|
|
213
218
|
target,
|
|
@@ -225,6 +230,128 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
225
230
|
indexBy(prop('id'))
|
|
226
231
|
)
|
|
227
232
|
|
|
233
|
+
function toDeploymentElement(doc: LangiumDocument) {
|
|
234
|
+
return (parsed: c4.DeploymentElement): c4.DeploymentElement | null => {
|
|
235
|
+
if (!c4.DeploymentElement.isDeploymentNode(parsed)) {
|
|
236
|
+
if (!parsed.links || parsed.links.length === 0) {
|
|
237
|
+
return parsed
|
|
238
|
+
}
|
|
239
|
+
const links = resolveLinks(doc, parsed.links)
|
|
240
|
+
return {
|
|
241
|
+
...parsed,
|
|
242
|
+
links
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
try {
|
|
246
|
+
const __kind = c4Specification.deployments[parsed.kind]
|
|
247
|
+
if (!__kind) {
|
|
248
|
+
logger.warn(`No kind '${parsed.kind}' found for ${parsed.id}`)
|
|
249
|
+
return null
|
|
250
|
+
}
|
|
251
|
+
let {
|
|
252
|
+
technology = __kind.technology,
|
|
253
|
+
notation = __kind.notation,
|
|
254
|
+
links,
|
|
255
|
+
style
|
|
256
|
+
} = parsed
|
|
257
|
+
return {
|
|
258
|
+
...parsed,
|
|
259
|
+
...notation && { notation },
|
|
260
|
+
...technology && { technology },
|
|
261
|
+
style: {
|
|
262
|
+
border: 'dashed',
|
|
263
|
+
opacity: 10,
|
|
264
|
+
...__kind.style,
|
|
265
|
+
...style
|
|
266
|
+
},
|
|
267
|
+
links: links ? resolveLinks(doc, links) : null
|
|
268
|
+
}
|
|
269
|
+
} catch (e) {
|
|
270
|
+
logWarnError(e)
|
|
271
|
+
}
|
|
272
|
+
return null
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const deploymentElements = pipe(
|
|
277
|
+
docs,
|
|
278
|
+
flatMap(d => map(d.c4Deployments, toDeploymentElement(d))),
|
|
279
|
+
filter(isTruthy),
|
|
280
|
+
// sort from root elements to nested, so that parent is always present
|
|
281
|
+
// Import to preserve the order from the source
|
|
282
|
+
sortByFqnHierarchically,
|
|
283
|
+
reduce(
|
|
284
|
+
(acc, el) => {
|
|
285
|
+
const parent = parentFqn(el.id)
|
|
286
|
+
if (parent && isNullish(acc[parent])) {
|
|
287
|
+
logWarnError(`No parent found for deployment element ${el.id}`)
|
|
288
|
+
return acc
|
|
289
|
+
}
|
|
290
|
+
acc[el.id] = el
|
|
291
|
+
return acc
|
|
292
|
+
},
|
|
293
|
+
{} as c4.ParsedLikeC4Model['deployments']['elements']
|
|
294
|
+
)
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
function toDeploymentRelation(doc: LangiumDocument) {
|
|
298
|
+
return ({
|
|
299
|
+
astPath,
|
|
300
|
+
source,
|
|
301
|
+
target,
|
|
302
|
+
kind,
|
|
303
|
+
links: unresolvedLinks,
|
|
304
|
+
id,
|
|
305
|
+
...model
|
|
306
|
+
}: ParsedAstDeploymentRelation): c4.DeploymentRelation | null => {
|
|
307
|
+
if (isNullish(deploymentElements[source.id]) || isNullish(deploymentElements[target.id])) {
|
|
308
|
+
logger.warn(
|
|
309
|
+
`Invalid deployment relation ${id} at ${doc.uri.path} ${astPath}, source: ${source.id}(${!!deploymentElements[
|
|
310
|
+
source.id
|
|
311
|
+
]}), target: ${target.id}(${!!deploymentElements[target.id]})`
|
|
312
|
+
)
|
|
313
|
+
return null
|
|
314
|
+
}
|
|
315
|
+
const links = unresolvedLinks ? resolveLinks(doc, unresolvedLinks) : null
|
|
316
|
+
|
|
317
|
+
if (isNonNullish(kind) && kind in c4Specification.relationships) {
|
|
318
|
+
return {
|
|
319
|
+
...c4Specification.relationships[kind],
|
|
320
|
+
...model,
|
|
321
|
+
...(links && { links }),
|
|
322
|
+
source,
|
|
323
|
+
target,
|
|
324
|
+
kind,
|
|
325
|
+
id
|
|
326
|
+
} satisfies c4.DeploymentRelation
|
|
327
|
+
}
|
|
328
|
+
return {
|
|
329
|
+
...links && { links },
|
|
330
|
+
...model,
|
|
331
|
+
source,
|
|
332
|
+
target,
|
|
333
|
+
id
|
|
334
|
+
} satisfies c4.DeploymentRelation
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const deploymentRelations = pipe(
|
|
339
|
+
docs,
|
|
340
|
+
flatMap(d => map(d.c4DeploymentRelations, toDeploymentRelation(d))),
|
|
341
|
+
filter(isTruthy),
|
|
342
|
+
reduce(
|
|
343
|
+
(acc, el) => {
|
|
344
|
+
if (isDefined(acc[el.id])) {
|
|
345
|
+
logWarnError(`Duplicate deployment relation ${el.id}`)
|
|
346
|
+
return acc
|
|
347
|
+
}
|
|
348
|
+
acc[el.id] = el
|
|
349
|
+
return acc
|
|
350
|
+
},
|
|
351
|
+
{} as c4.ParsedLikeC4Model['deployments']['relations']
|
|
352
|
+
)
|
|
353
|
+
)
|
|
354
|
+
|
|
228
355
|
function toC4View(doc: LangiumDocument) {
|
|
229
356
|
const docUri = doc.uri.toString()
|
|
230
357
|
return (parsedAstView: ParsedAstView): c4.LikeC4View => {
|
|
@@ -273,7 +400,7 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
273
400
|
if (!parsedViews.some(v => v.id === 'index')) {
|
|
274
401
|
parsedViews.unshift({
|
|
275
402
|
__: 'element',
|
|
276
|
-
id: 'index' as
|
|
403
|
+
id: 'index' as ViewId,
|
|
277
404
|
title: 'Landscape view',
|
|
278
405
|
description: null,
|
|
279
406
|
tags: null,
|
|
@@ -301,12 +428,17 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
301
428
|
specification: {
|
|
302
429
|
tags: Array.from(c4Specification.tags),
|
|
303
430
|
elements: c4Specification.elements,
|
|
304
|
-
relationships: c4Specification.relationships
|
|
431
|
+
relationships: c4Specification.relationships,
|
|
432
|
+
deployments: c4Specification.deployments
|
|
305
433
|
},
|
|
306
434
|
elements,
|
|
307
435
|
relations,
|
|
308
436
|
globals,
|
|
309
|
-
views
|
|
437
|
+
views,
|
|
438
|
+
deployments: {
|
|
439
|
+
elements: deploymentElements,
|
|
440
|
+
relations: deploymentRelations
|
|
441
|
+
}
|
|
310
442
|
}
|
|
311
443
|
}
|
|
312
444
|
|
|
@@ -335,8 +467,8 @@ export class LikeC4ModelBuilder {
|
|
|
335
467
|
let parsed = [] as URI[]
|
|
336
468
|
try {
|
|
337
469
|
logger.debug(`[ModelBuilder] onValidated (${docs.length} docs)`)
|
|
338
|
-
for (const doc of
|
|
339
|
-
parsed.push(doc.uri)
|
|
470
|
+
for (const doc of docs) {
|
|
471
|
+
parsed.push(parser.parse(doc).uri)
|
|
340
472
|
}
|
|
341
473
|
} catch (e) {
|
|
342
474
|
logWarnError(e)
|
|
@@ -356,13 +488,13 @@ export class LikeC4ModelBuilder {
|
|
|
356
488
|
* Otherwise, the model may be incomplete.
|
|
357
489
|
*/
|
|
358
490
|
public unsafeSyncBuildModel(): c4.ParsedLikeC4Model | null {
|
|
491
|
+
const docs = this.documents()
|
|
492
|
+
if (docs.length === 0) {
|
|
493
|
+
logger.debug('[ModelBuilder] No documents to build model from')
|
|
494
|
+
return null
|
|
495
|
+
}
|
|
359
496
|
const cache = this.services.WorkspaceCache as WorkspaceCache<string, c4.ParsedLikeC4Model | null>
|
|
360
497
|
return cache.get(CACHE_KEY_PARSED_MODEL, () => {
|
|
361
|
-
const docs = this.documents()
|
|
362
|
-
if (docs.length === 0) {
|
|
363
|
-
logger.debug('[ModelBuilder] No documents to build model from')
|
|
364
|
-
return null
|
|
365
|
-
}
|
|
366
498
|
logger.debug(`[ModelBuilder] buildModel (${docs.length} docs)`)
|
|
367
499
|
return buildModel(this.services, docs)
|
|
368
500
|
})
|
|
@@ -370,8 +502,9 @@ export class LikeC4ModelBuilder {
|
|
|
370
502
|
|
|
371
503
|
public async buildModel(cancelToken?: Cancellation.CancellationToken): Promise<c4.ParsedLikeC4Model | null> {
|
|
372
504
|
const cache = this.services.WorkspaceCache as WorkspaceCache<string, c4.ParsedLikeC4Model | null>
|
|
373
|
-
|
|
374
|
-
|
|
505
|
+
const cached = cache.get(CACHE_KEY_PARSED_MODEL)
|
|
506
|
+
if (cached) {
|
|
507
|
+
return cached
|
|
375
508
|
}
|
|
376
509
|
return await this.services.shared.workspace.WorkspaceLock.read(async () => {
|
|
377
510
|
if (cancelToken) {
|
|
@@ -381,7 +514,7 @@ export class LikeC4ModelBuilder {
|
|
|
381
514
|
})
|
|
382
515
|
}
|
|
383
516
|
|
|
384
|
-
private previousViews: Record<
|
|
517
|
+
private previousViews: Record<ViewId, c4.ComputedView> = {}
|
|
385
518
|
|
|
386
519
|
/**
|
|
387
520
|
* WARNING:
|
|
@@ -392,13 +525,10 @@ export class LikeC4ModelBuilder {
|
|
|
392
525
|
const cache = this.services.WorkspaceCache as WorkspaceCache<string, c4.ComputedLikeC4Model>
|
|
393
526
|
const viewsCache = this.services.WorkspaceCache as WorkspaceCache<string, c4.ComputedView | null>
|
|
394
527
|
return cache.get(CACHE_KEY_COMPUTED_MODEL, () => {
|
|
395
|
-
const
|
|
396
|
-
|
|
528
|
+
const computeView = mkComputeView(model)
|
|
397
529
|
const allViews = [] as c4.ComputedView[]
|
|
398
530
|
for (const view of values(model.views)) {
|
|
399
|
-
const result =
|
|
400
|
-
? computeView(view, index)
|
|
401
|
-
: computeDynamicView(view, index)
|
|
531
|
+
const result = computeView(view)
|
|
402
532
|
if (!result.isSuccess) {
|
|
403
533
|
logWarnError(result.error)
|
|
404
534
|
continue
|
|
@@ -414,10 +544,15 @@ export class LikeC4ModelBuilder {
|
|
|
414
544
|
})
|
|
415
545
|
this.previousViews = { ...views }
|
|
416
546
|
return {
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
547
|
+
...structuredClone(
|
|
548
|
+
pick(model, [
|
|
549
|
+
'specification',
|
|
550
|
+
'elements',
|
|
551
|
+
'relations',
|
|
552
|
+
'globals',
|
|
553
|
+
'deployments'
|
|
554
|
+
])
|
|
555
|
+
),
|
|
421
556
|
views
|
|
422
557
|
}
|
|
423
558
|
})
|
|
@@ -443,7 +578,7 @@ export class LikeC4ModelBuilder {
|
|
|
443
578
|
}
|
|
444
579
|
|
|
445
580
|
public async computeView(
|
|
446
|
-
viewId:
|
|
581
|
+
viewId: ViewId,
|
|
447
582
|
cancelToken?: Cancellation.CancellationToken
|
|
448
583
|
): Promise<c4.ComputedView | null> {
|
|
449
584
|
const cache = this.services.WorkspaceCache as WorkspaceCache<string, c4.ComputedView | null>
|
|
@@ -462,10 +597,7 @@ export class LikeC4ModelBuilder {
|
|
|
462
597
|
logger.warn(`[ModelBuilder] Cannot find view ${viewId}`)
|
|
463
598
|
return null
|
|
464
599
|
}
|
|
465
|
-
const
|
|
466
|
-
const result = isElementView(view)
|
|
467
|
-
? computeView(view, index)
|
|
468
|
-
: computeDynamicView(view, index)
|
|
600
|
+
const result = mkComputeView(model)(view)
|
|
469
601
|
if (!result.isSuccess) {
|
|
470
602
|
logError(result.error)
|
|
471
603
|
return null
|
|
@@ -490,8 +622,11 @@ export class LikeC4ModelBuilder {
|
|
|
490
622
|
}
|
|
491
623
|
|
|
492
624
|
const previous = this.previousViews[viewId]
|
|
493
|
-
|
|
494
|
-
|
|
625
|
+
if (previous && eq(computedView, previous)) {
|
|
626
|
+
computedView = previous
|
|
627
|
+
} else {
|
|
628
|
+
this.previousViews[viewId] = computedView
|
|
629
|
+
}
|
|
495
630
|
|
|
496
631
|
return computedView
|
|
497
632
|
})
|