@likec4/language-server 1.17.1 → 1.19.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.
Files changed (268) hide show
  1. package/contrib/likec4.tmLanguage.json +1 -1
  2. package/dist/LikeC4FileSystem.d.ts +13 -0
  3. package/dist/LikeC4FileSystem.js +27 -0
  4. package/dist/Rpc.d.ts +9 -0
  5. package/dist/Rpc.js +126 -0
  6. package/dist/ast.d.ts +200 -0
  7. package/dist/ast.js +276 -0
  8. package/dist/browser.d.ts +6 -20
  9. package/dist/browser.js +13 -0
  10. package/dist/formatting/LikeC4Formatter.d.ts +27 -0
  11. package/dist/formatting/LikeC4Formatter.js +261 -0
  12. package/dist/formatting/utils.d.ts +6 -0
  13. package/dist/formatting/utils.js +15 -0
  14. package/dist/generated/ast.d.ts +1242 -0
  15. package/dist/generated/ast.js +1945 -0
  16. package/dist/generated/grammar.d.ts +6 -0
  17. package/dist/generated/grammar.js +3 -0
  18. package/dist/generated/module.d.ts +9 -0
  19. package/dist/generated/module.js +23 -0
  20. package/dist/generated-lib/icons.d.ts +1 -0
  21. package/dist/{likec4lib.mjs → generated-lib/icons.js} +1 -6
  22. package/dist/index.d.ts +8 -31
  23. package/dist/index.js +13 -0
  24. package/dist/like-c4.langium +845 -0
  25. package/dist/likec4lib.d.ts +4 -6
  26. package/dist/likec4lib.js +4 -0
  27. package/dist/logger.d.ts +7 -0
  28. package/dist/logger.js +73 -0
  29. package/dist/lsp/CodeLensProvider.d.ts +9 -0
  30. package/dist/lsp/CodeLensProvider.js +40 -0
  31. package/dist/lsp/CompletionProvider.d.ts +6 -0
  32. package/dist/lsp/CompletionProvider.js +135 -0
  33. package/dist/lsp/DocumentHighlightProvider.d.ts +9 -0
  34. package/dist/lsp/DocumentHighlightProvider.js +10 -0
  35. package/dist/lsp/DocumentLinkProvider.d.ts +11 -0
  36. package/dist/lsp/DocumentLinkProvider.js +49 -0
  37. package/dist/lsp/DocumentSymbolProvider.d.ts +23 -0
  38. package/dist/lsp/DocumentSymbolProvider.js +202 -0
  39. package/dist/lsp/HoverProvider.d.ts +10 -0
  40. package/dist/lsp/HoverProvider.js +69 -0
  41. package/dist/lsp/RenameProvider.d.ts +5 -0
  42. package/dist/lsp/RenameProvider.js +6 -0
  43. package/dist/lsp/SemanticTokenProvider.d.ts +7 -0
  44. package/dist/lsp/SemanticTokenProvider.js +297 -0
  45. package/dist/lsp/index.d.ts +7 -0
  46. package/dist/lsp/index.js +7 -0
  47. package/dist/model/deployments-index.d.ts +60 -0
  48. package/dist/model/deployments-index.js +181 -0
  49. package/dist/model/fqn-computation.d.ts +3 -0
  50. package/dist/model/fqn-computation.js +72 -0
  51. package/dist/model/fqn-index.d.ts +25 -0
  52. package/dist/model/fqn-index.js +96 -0
  53. package/dist/model/index.d.ts +6 -0
  54. package/dist/model/index.js +6 -0
  55. package/dist/model/model-builder.d.ts +32 -0
  56. package/dist/model/model-builder.js +598 -0
  57. package/dist/model/model-locator.d.ts +23 -0
  58. package/dist/model/model-locator.js +126 -0
  59. package/dist/model/model-parser-where.d.ts +3 -0
  60. package/dist/model/model-parser-where.js +70 -0
  61. package/dist/model/model-parser.d.ts +292 -0
  62. package/dist/model/model-parser.js +72 -0
  63. package/dist/model/parser/Base.d.ts +28 -0
  64. package/dist/model/parser/Base.js +87 -0
  65. package/dist/model/parser/DeploymentModelParser.d.ts +33 -0
  66. package/dist/model/parser/DeploymentModelParser.js +162 -0
  67. package/dist/model/parser/DeploymentViewParser.d.ts +38 -0
  68. package/dist/model/parser/DeploymentViewParser.js +98 -0
  69. package/dist/model/parser/FqnRefParser.d.ts +29 -0
  70. package/dist/model/parser/FqnRefParser.js +108 -0
  71. package/dist/model/parser/GlobalsParser.d.ts +66 -0
  72. package/dist/model/parser/GlobalsParser.js +80 -0
  73. package/dist/model/parser/ModelParser.d.ts +27 -0
  74. package/dist/model/parser/ModelParser.js +122 -0
  75. package/dist/model/parser/PredicatesParser.d.ts +34 -0
  76. package/dist/model/parser/PredicatesParser.js +272 -0
  77. package/dist/model/parser/SpecificationParser.d.ts +27 -0
  78. package/dist/model/parser/SpecificationParser.js +120 -0
  79. package/dist/model/parser/ViewsParser.d.ts +64 -0
  80. package/dist/model/parser/ViewsParser.js +377 -0
  81. package/dist/model-change/ModelChanges.d.ts +15 -0
  82. package/dist/model-change/ModelChanges.js +89 -0
  83. package/dist/model-change/changeElementStyle.d.ts +16 -0
  84. package/dist/model-change/changeElementStyle.js +136 -0
  85. package/dist/model-change/changeViewLayout.d.ts +12 -0
  86. package/dist/model-change/changeViewLayout.js +32 -0
  87. package/dist/model-change/saveManualLayout.d.ts +11 -0
  88. package/dist/model-change/saveManualLayout.js +27 -0
  89. package/dist/module.d.ts +62 -0
  90. package/dist/module.js +123 -0
  91. package/dist/protocol.d.ts +27 -27
  92. package/dist/protocol.js +14 -0
  93. package/dist/references/index.d.ts +3 -0
  94. package/dist/references/index.js +3 -0
  95. package/dist/references/name-provider.d.ts +9 -0
  96. package/dist/references/name-provider.js +33 -0
  97. package/dist/references/scope-computation.d.ts +20 -0
  98. package/dist/references/scope-computation.js +281 -0
  99. package/dist/references/scope-provider.d.ts +16 -0
  100. package/dist/references/scope-provider.js +165 -0
  101. package/dist/shared/NodeKindProvider.d.ts +15 -0
  102. package/dist/shared/NodeKindProvider.js +108 -0
  103. package/dist/shared/WorkspaceManager.d.ts +18 -0
  104. package/dist/shared/WorkspaceManager.js +36 -0
  105. package/dist/shared/WorkspaceSymbolProvider.d.ts +3 -0
  106. package/dist/shared/WorkspaceSymbolProvider.js +3 -0
  107. package/dist/shared/index.d.ts +3 -0
  108. package/dist/shared/index.js +3 -0
  109. package/dist/test/index.d.ts +1 -0
  110. package/dist/test/index.js +1 -0
  111. package/dist/test/setup.d.ts +1 -0
  112. package/dist/test/setup.js +7 -0
  113. package/dist/test/testServices.d.ts +22 -0
  114. package/dist/test/testServices.js +119 -0
  115. package/dist/utils/elementRef.d.ts +11 -0
  116. package/dist/utils/elementRef.js +15 -0
  117. package/dist/utils/fqnRef.d.ts +8 -0
  118. package/dist/utils/fqnRef.js +46 -0
  119. package/dist/utils/index.d.ts +1 -0
  120. package/dist/utils/index.js +1 -0
  121. package/dist/utils/printDocs.d.ts +2 -0
  122. package/dist/utils/printDocs.js +1 -0
  123. package/dist/utils/stringHash.d.ts +1 -0
  124. package/dist/utils/stringHash.js +5 -0
  125. package/dist/validation/_shared.d.ts +3 -0
  126. package/dist/validation/_shared.js +22 -0
  127. package/dist/validation/deployment-checks.d.ts +6 -0
  128. package/dist/validation/deployment-checks.js +114 -0
  129. package/dist/validation/dynamic-view-rule.d.ts +4 -0
  130. package/dist/validation/dynamic-view-rule.js +16 -0
  131. package/dist/validation/dynamic-view-step.d.ts +4 -0
  132. package/dist/validation/dynamic-view-step.js +33 -0
  133. package/dist/validation/element.d.ts +4 -0
  134. package/dist/validation/element.js +49 -0
  135. package/dist/validation/index.d.ts +15 -0
  136. package/dist/validation/index.js +152 -0
  137. package/dist/validation/property-checks.d.ts +6 -0
  138. package/dist/validation/property-checks.js +38 -0
  139. package/dist/validation/relation.d.ts +5 -0
  140. package/dist/validation/relation.js +56 -0
  141. package/dist/validation/specification.d.ts +11 -0
  142. package/dist/validation/specification.js +136 -0
  143. package/dist/validation/view-predicates/element-with.d.ts +4 -0
  144. package/dist/validation/view-predicates/element-with.js +30 -0
  145. package/dist/validation/view-predicates/expanded-element.d.ts +4 -0
  146. package/dist/validation/view-predicates/expanded-element.js +11 -0
  147. package/dist/validation/view-predicates/expression-v2.d.ts +5 -0
  148. package/dist/validation/view-predicates/expression-v2.js +83 -0
  149. package/dist/validation/view-predicates/incoming.d.ts +4 -0
  150. package/dist/validation/view-predicates/incoming.js +15 -0
  151. package/dist/validation/view-predicates/index.d.ts +6 -0
  152. package/dist/validation/view-predicates/index.js +6 -0
  153. package/dist/validation/view-predicates/outgoing.d.ts +4 -0
  154. package/dist/validation/view-predicates/outgoing.js +15 -0
  155. package/dist/validation/view-predicates/relation-with.d.ts +4 -0
  156. package/dist/validation/view-predicates/relation-with.js +12 -0
  157. package/dist/validation/view.d.ts +4 -0
  158. package/dist/validation/view.js +23 -0
  159. package/dist/view-utils/assignNavigateTo.d.ts +2 -0
  160. package/dist/view-utils/assignNavigateTo.js +25 -0
  161. package/dist/view-utils/index.d.ts +2 -0
  162. package/dist/view-utils/index.js +2 -0
  163. package/dist/view-utils/manual-layout.d.ts +7 -0
  164. package/dist/view-utils/manual-layout.js +99 -0
  165. package/dist/view-utils/resolve-relative-paths.d.ts +2 -0
  166. package/dist/view-utils/resolve-relative-paths.js +78 -0
  167. package/package.json +42 -73
  168. package/src/LikeC4FileSystem.ts +22 -21
  169. package/src/Rpc.ts +6 -3
  170. package/src/ast.ts +136 -172
  171. package/src/browser.ts +10 -11
  172. package/src/generated/ast.ts +656 -40
  173. package/src/generated/grammar.ts +1 -1
  174. package/src/index.ts +11 -8
  175. package/src/like-c4.langium +173 -22
  176. package/src/logger.ts +41 -57
  177. package/src/lsp/CodeLensProvider.ts +0 -1
  178. package/src/lsp/CompletionProvider.ts +20 -5
  179. package/src/lsp/DocumentSymbolProvider.ts +5 -2
  180. package/src/lsp/HoverProvider.ts +37 -3
  181. package/src/lsp/SemanticTokenProvider.ts +58 -32
  182. package/src/model/deployments-index.ts +222 -0
  183. package/src/model/fqn-computation.ts +1 -1
  184. package/src/model/fqn-index.ts +0 -1
  185. package/src/model/index.ts +1 -0
  186. package/src/model/model-builder.ts +176 -39
  187. package/src/model/model-locator.ts +36 -7
  188. package/src/model/model-parser.ts +69 -1119
  189. package/src/model/parser/Base.ts +107 -0
  190. package/src/model/parser/DeploymentModelParser.ts +192 -0
  191. package/src/model/parser/DeploymentViewParser.ts +116 -0
  192. package/src/model/parser/FqnRefParser.ts +118 -0
  193. package/src/model/parser/GlobalsParser.ts +96 -0
  194. package/src/model/parser/ModelParser.ts +141 -0
  195. package/src/model/parser/PredicatesParser.ts +291 -0
  196. package/src/model/parser/SpecificationParser.ts +133 -0
  197. package/src/model/parser/ViewsParser.ts +428 -0
  198. package/src/model-change/changeViewLayout.ts +2 -2
  199. package/src/module.ts +26 -21
  200. package/src/protocol.ts +10 -6
  201. package/src/references/index.ts +1 -0
  202. package/src/references/name-provider.ts +37 -0
  203. package/src/references/scope-computation.ts +130 -21
  204. package/src/references/scope-provider.ts +68 -35
  205. package/src/shared/NodeKindProvider.ts +15 -3
  206. package/src/{elementRef.ts → utils/elementRef.ts} +1 -1
  207. package/src/utils/fqnRef.ts +56 -0
  208. package/src/utils/stringHash.ts +2 -2
  209. package/src/validation/_shared.ts +6 -5
  210. package/src/validation/deployment-checks.ts +131 -0
  211. package/src/validation/dynamic-view-step.ts +1 -1
  212. package/src/validation/index.ts +104 -6
  213. package/src/validation/relation.ts +1 -1
  214. package/src/validation/view-predicates/expression-v2.ts +101 -0
  215. package/src/validation/view-predicates/index.ts +1 -0
  216. package/src/view-utils/assignNavigateTo.ts +6 -5
  217. package/src/view-utils/index.ts +0 -1
  218. package/src/view-utils/manual-layout.ts +25 -0
  219. package/dist/browser.cjs +0 -25
  220. package/dist/browser.d.cts +0 -23
  221. package/dist/browser.d.mts +0 -23
  222. package/dist/browser.mjs +0 -20
  223. package/dist/index.cjs +0 -53
  224. package/dist/index.d.cts +0 -34
  225. package/dist/index.d.mts +0 -34
  226. package/dist/index.mjs +0 -46
  227. package/dist/likec4lib.cjs +0 -1546
  228. package/dist/likec4lib.d.cts +0 -6
  229. package/dist/likec4lib.d.mts +0 -6
  230. package/dist/model-graph/index.cjs +0 -10
  231. package/dist/model-graph/index.d.cts +0 -81
  232. package/dist/model-graph/index.d.mts +0 -81
  233. package/dist/model-graph/index.d.ts +0 -81
  234. package/dist/model-graph/index.mjs +0 -1
  235. package/dist/protocol.cjs +0 -25
  236. package/dist/protocol.d.cts +0 -45
  237. package/dist/protocol.d.mts +0 -45
  238. package/dist/protocol.mjs +0 -17
  239. package/dist/shared/language-server.BIbAD1T-.mjs +0 -6292
  240. package/dist/shared/language-server.BQRvVmE0.d.cts +0 -1303
  241. package/dist/shared/language-server.BysPcTxr.d.ts +0 -1303
  242. package/dist/shared/language-server.D2QdbOJO.cjs +0 -1995
  243. package/dist/shared/language-server.DGrBGmsd.mjs +0 -1981
  244. package/dist/shared/language-server.DKV_FdPN.cjs +0 -6304
  245. package/dist/shared/language-server._wkyPgso.d.mts +0 -1303
  246. package/src/model-graph/LikeC4ModelGraph.ts +0 -338
  247. package/src/model-graph/compute-view/__test__/fixture.ts +0 -630
  248. package/src/model-graph/compute-view/compute.ts +0 -788
  249. package/src/model-graph/compute-view/index.ts +0 -33
  250. package/src/model-graph/compute-view/predicates.ts +0 -509
  251. package/src/model-graph/dynamic-view/__test__/fixture.ts +0 -61
  252. package/src/model-graph/dynamic-view/compute.ts +0 -313
  253. package/src/model-graph/dynamic-view/index.ts +0 -29
  254. package/src/model-graph/index.ts +0 -3
  255. package/src/model-graph/utils/applyCustomElementProperties.ts +0 -65
  256. package/src/model-graph/utils/applyCustomRelationProperties.ts +0 -41
  257. package/src/model-graph/utils/applyViewRuleStyles.ts +0 -49
  258. package/src/model-graph/utils/buildComputeNodes.ts +0 -113
  259. package/src/model-graph/utils/buildElementNotations.ts +0 -63
  260. package/src/model-graph/utils/elementExpressionToPredicate.ts +0 -39
  261. package/src/model-graph/utils/relationExpressionToPredicates.ts +0 -43
  262. package/src/model-graph/utils/sortNodes.ts +0 -105
  263. package/src/model-graph/utils/uniqueTags.test.ts +0 -42
  264. package/src/model-graph/utils/uniqueTags.ts +0 -19
  265. package/src/utils/graphlib.ts +0 -9
  266. package/src/view-utils/resolve-extended-views.ts +0 -66
  267. package/src/view-utils/resolve-global-rules.ts +0 -88
  268. package/src/view-utils/view-hash.ts +0 -27
@@ -1,1132 +1,82 @@
1
- import { type HexColorLiteral, invariant, isNonEmptyArray, nonexhaustive } from '@likec4/core'
2
- import type * as c4 from '@likec4/core'
3
- import type { AstNode, LangiumDocument } from 'langium'
4
- import { AstUtils, CstUtils } from 'langium'
5
- import { filter, first, flatMap, isDefined, isNonNullish, isTruthy, map, mapToObj, pipe } from 'remeda'
6
- import stripIndent from 'strip-indent'
7
- import type { Writable } from 'type-fest'
8
- import type {
9
- ChecksFromDiagnostics,
10
- FqnIndexedDocument,
11
- ParsedAstDynamicView,
12
- ParsedAstElement,
13
- ParsedAstElementView,
14
- ParsedAstGlobals,
15
- ParsedAstRelation,
16
- ParsedLikeC4LangiumDocument,
17
- ParsedLink
18
- } from '../ast'
19
- import {
20
- ast,
21
- checksFromDiagnostics,
22
- cleanParsedModel,
23
- isFqnIndexedDocument,
24
- parseAstOpacityProperty,
25
- resolveRelationPoints,
26
- streamModel,
27
- toAutoLayout,
28
- toColor,
29
- toElementStyle,
30
- toRelationshipStyleExcludeDefaults,
31
- ViewOps
32
- } from '../ast'
33
- import { elementRef, getFqnElementRef } from '../elementRef'
34
- import { isGlobalStyle, isGlobalStyleGroup, type NotationProperty } from '../generated/ast'
35
- import { logError, logger, logWarnError } from '../logger'
1
+ import { invariant } from '@likec4/core'
2
+ import type { LangiumDocument } from 'langium'
3
+ import DefaultWeakMap from 'mnemonist/default-weak-map'
4
+ import { pipe } from 'remeda'
5
+ import type { LikeC4DocumentProps, ParsedLikeC4LangiumDocument } from '../ast'
6
+ import { isFqnIndexedDocument } from '../ast'
36
7
  import type { LikeC4Services } from '../module'
37
- import { stringHash } from '../utils'
38
- import { deserializeFromComment, hasManualLayout } from '../view-utils/manual-layout'
39
- import type { FqnIndex } from './fqn-index'
40
- import { parseWhereClause } from './model-parser-where'
41
-
42
- const { getDocument } = AstUtils
8
+ import { BaseParser } from './parser/Base'
9
+ import { DeploymentModelParser } from './parser/DeploymentModelParser'
10
+ import { DeploymentViewParser } from './parser/DeploymentViewParser'
11
+ import { ExpressionV2Parser } from './parser/FqnRefParser'
12
+ import { GlobalsParser } from './parser/GlobalsParser'
13
+ import { ModelParser } from './parser/ModelParser'
14
+ import { PredicatesParser } from './parser/PredicatesParser'
15
+ import { SpecificationParser } from './parser/SpecificationParser'
16
+ import { ViewsParser } from './parser/ViewsParser'
43
17
 
44
18
  export type ModelParsedListener = () => void
45
19
 
46
- function toSingleLine<T extends string | undefined | null>(str: T): T {
47
- return (isNonNullish(str) ? removeIndent(str).split('\n').join(' ') : undefined) as T
48
- }
49
-
50
- function removeIndent<T extends string | undefined | null>(str: T): T {
51
- return (isNonNullish(str) ? stripIndent(str).trim() : undefined) as T
20
+ const DocumentParserFromMixins = pipe(
21
+ BaseParser,
22
+ ExpressionV2Parser,
23
+ ModelParser,
24
+ DeploymentModelParser,
25
+ DeploymentViewParser,
26
+ PredicatesParser,
27
+ SpecificationParser,
28
+ ViewsParser,
29
+ GlobalsParser
30
+ )
31
+
32
+ export class DocumentParser extends DocumentParserFromMixins {
52
33
  }
53
34
 
54
- export type IsValidFn = ChecksFromDiagnostics['isValid']
55
-
56
35
  export class LikeC4ModelParser {
57
- private fqnIndex: FqnIndex
58
- constructor(private services: LikeC4Services) {
59
- this.fqnIndex = services.likec4.FqnIndex
60
- logger.debug(`[ModelParser] Created`)
61
- }
62
-
63
- parse(doc: LangiumDocument | LangiumDocument[]): ParsedLikeC4LangiumDocument[] {
64
- const docs = Array.isArray(doc) ? doc : [doc]
65
- const result = [] as ParsedLikeC4LangiumDocument[]
66
- for (const doc of docs) {
67
- if (!isFqnIndexedDocument(doc)) {
68
- logger.warn(`Not a FqnIndexedDocument: ${doc.uri.toString(true)}`)
69
- continue
70
- }
71
- try {
72
- result.push(this.parseLikeC4Document(doc))
73
- } catch (cause) {
74
- logError(new Error(`Error parsing document ${doc.uri.toString()}`, { cause }))
75
- }
76
- }
77
- return result
78
- }
79
-
80
- protected parseLikeC4Document(_doc: FqnIndexedDocument) {
81
- const doc = cleanParsedModel(_doc)
82
- const { isValid } = checksFromDiagnostics(doc)
83
- this.parseSpecification(doc, isValid)
84
- this.parseModel(doc, isValid)
85
- this.parseGlobal(doc, isValid)
86
- this.parseViews(doc, isValid)
87
- return doc
88
- }
89
-
90
- private parseSpecification(doc: ParsedLikeC4LangiumDocument, isValid: IsValidFn) {
91
- const { parseResult, c4Specification } = doc
92
-
93
- const specifications = parseResult.value.specifications.filter(isValid)
94
- const element_specs = specifications.flatMap(s => s.elements.filter(isValid))
95
- for (const { kind, props } of element_specs) {
96
- try {
97
- const kindName = kind.name as c4.ElementKind
98
- if (!isTruthy(kindName)) {
99
- continue
100
- }
101
- if (kindName in c4Specification.elements) {
102
- logger.warn(`Element kind "${kindName}" is already defined`)
103
- continue
104
- }
105
- const style = props.find(ast.isElementStyleProperty)
106
- const bodyProps = mapToObj(
107
- props.filter(ast.isSpecificationElementStringProperty).filter(p => isNonNullish(p.value)) ?? [],
108
- p => [p.key, removeIndent(p.value)] as const
109
- )
110
- c4Specification.elements[kindName] = {
111
- ...bodyProps,
112
- style: {
113
- ...toElementStyle(style?.props, isValid)
114
- }
115
- }
116
- } catch (e) {
117
- logWarnError(e)
118
- }
119
- }
120
-
121
- const relations_specs = specifications.flatMap(s => s.relationships.filter(isValid))
122
- for (const { kind, props } of relations_specs) {
123
- try {
124
- const kindName = kind.name as c4.RelationshipKind
125
- if (!isTruthy(kindName)) {
126
- continue
127
- }
128
- if (kindName in c4Specification.relationships) {
129
- logger.warn(`Relationship kind "${kindName}" is already defined`)
130
- continue
131
- }
132
- const bodyProps = mapToObj(
133
- props.filter(ast.isSpecificationRelationshipStringProperty).filter(p => isNonNullish(p.value)) ?? [],
134
- p => [p.key, removeIndent(p.value)]
135
- )
136
- c4Specification.relationships[kindName] = {
137
- ...bodyProps,
138
- ...toRelationshipStyleExcludeDefaults(props)
139
- }
140
- } catch (e) {
141
- logWarnError(e)
142
- }
143
- }
144
-
145
- const tags_specs = specifications.flatMap(s => s.tags.filter(isValid))
146
- for (const tagSpec of tags_specs) {
147
- const tag = tagSpec.tag.name as c4.Tag
148
- if (isTruthy(tag)) {
149
- c4Specification.tags.add(tag)
150
- }
151
- }
152
-
153
- const colors_specs = specifications.flatMap(s => s.colors.filter(isValid))
154
- for (const { name, color } of colors_specs) {
155
- try {
156
- const colorName = name.name as c4.CustomColor
157
- if (colorName in c4Specification.colors) {
158
- logger.warn(`Custom color "${colorName}" is already defined`)
159
- continue
160
- }
161
-
162
- c4Specification.colors[colorName] = {
163
- color: color as HexColorLiteral
164
- }
165
- } catch (e) {
166
- logWarnError(e)
167
- }
168
- }
169
- }
170
-
171
- private parseModel(doc: ParsedLikeC4LangiumDocument, isValid: IsValidFn) {
172
- for (const el of streamModel(doc, isValid)) {
173
- if (ast.isElement(el)) {
174
- try {
175
- doc.c4Elements.push(this.parseElement(el, isValid))
176
- } catch (e) {
177
- logWarnError(e)
178
- }
179
- continue
180
- }
181
- if (ast.isRelation(el)) {
182
- try {
183
- doc.c4Relations.push(this.parseRelation(el))
184
- } catch (e) {
185
- logWarnError(e)
186
- }
187
- continue
188
- }
189
- nonexhaustive(el)
190
- }
191
- }
192
-
193
- private parseElement(astNode: ast.Element, isValid: IsValidFn): ParsedAstElement {
194
- const id = this.resolveFqn(astNode)
195
- const kind = astNode.kind.$refText as c4.ElementKind
196
- const tags = this.convertTags(astNode.body)
197
- const stylePropsAst = astNode.body?.props.find(ast.isElementStyleProperty)?.props
198
- const style = toElementStyle(stylePropsAst, isValid)
199
- const metadata = this.getMetadata(astNode.body?.props.find(ast.isMetadataProperty))
200
- const astPath = this.getAstNodePath(astNode)
201
-
202
- let [title, description, technology] = astNode.props ?? []
203
-
204
- const bodyProps = mapToObj(
205
- astNode.body?.props.filter(ast.isElementStringProperty) ?? [],
206
- p => [p.key, p.value || undefined]
207
- )
208
-
209
- title = toSingleLine(title ?? bodyProps.title)
210
- description = removeIndent(bodyProps.description ?? description)
211
- technology = toSingleLine(bodyProps.technology ?? technology)
212
-
213
- const links = this.convertLinks(astNode.body)
214
-
215
- // Property has higher priority than from style
216
- const iconProp = astNode.body?.props.find(ast.isIconProperty)
217
- if (iconProp) {
218
- const value = iconProp.libicon?.ref?.name ?? iconProp.value
219
- if (isTruthy(value)) {
220
- style.icon = value as c4.IconUrl
221
- }
222
- }
223
-
224
- return {
225
- id,
226
- kind,
227
- astPath,
228
- title: title ?? astNode.name,
229
- ...(metadata && { metadata }),
230
- ...(tags && { tags }),
231
- ...(links && isNonEmptyArray(links) && { links }),
232
- ...(isTruthy(technology) && { technology }),
233
- ...(isTruthy(description) && { description }),
234
- style
235
- }
236
- }
237
-
238
- private parseRelation(astNode: ast.Relation): ParsedAstRelation {
239
- const coupling = resolveRelationPoints(astNode)
240
- const target = this.resolveFqn(coupling.target)
241
- const source = this.resolveFqn(coupling.source)
242
- const tags = this.convertTags(astNode) ?? this.convertTags(astNode.body)
243
- const links = this.convertLinks(astNode.body)
244
- const kind = astNode.kind?.ref?.name as (c4.RelationshipKind | undefined)
245
- const metadata = this.getMetadata(astNode.body?.props.find(ast.isMetadataProperty))
246
- const astPath = this.getAstNodePath(astNode)
247
-
248
- const bodyProps = mapToObj(
249
- astNode.body?.props.filter(ast.isRelationStringProperty).filter(p => isNonNullish(p.value)) ?? [],
250
- p => [p.key, p.value]
251
- )
252
-
253
- const navigateTo = pipe(
254
- astNode.body?.props ?? [],
255
- filter(ast.isRelationNavigateToProperty),
256
- map(p => p.value.view.ref?.name),
257
- filter(isTruthy),
258
- first()
259
- )
260
-
261
- const title = removeIndent(astNode.title ?? bodyProps.title) ?? ''
262
- const description = removeIndent(bodyProps.description)
263
- const technology = removeIndent(astNode.technology) ?? toSingleLine(bodyProps.technology)
264
-
265
- const styleProp = astNode.body?.props.find(ast.isRelationStyleProperty)
266
- const id = stringHash(
267
- astPath,
268
- source,
269
- target
270
- ) as c4.RelationID
271
- return {
272
- id,
273
- astPath,
274
- source,
275
- target,
276
- title,
277
- ...(metadata && { metadata }),
278
- ...(isTruthy(technology) && { technology }),
279
- ...(isTruthy(description) && { description }),
280
- ...(kind && { kind }),
281
- ...(tags && { tags }),
282
- ...(isNonEmptyArray(links) && { links }),
283
- ...toRelationshipStyleExcludeDefaults(styleProp?.props),
284
- ...(navigateTo && { navigateTo: navigateTo as c4.ViewID })
285
- }
286
- }
287
-
288
- private parseGlobal(doc: ParsedLikeC4LangiumDocument, isValid: IsValidFn) {
289
- const { parseResult, c4Globals } = doc
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
-
311
- const styles = globals.flatMap(r => r.styles.filter(isValid))
312
- for (const style of styles) {
313
- try {
314
- const globalStyleId = style.id.name as c4.GlobalStyleID
315
- if (!isTruthy(globalStyleId)) {
316
- continue
317
- }
318
- if (globalStyleId in c4Globals.styles) {
319
- logger.warn(`Global style named "${globalStyleId}" is already defined`)
320
- continue
321
- }
322
-
323
- const styles = this.parseGlobalStyleOrGroup(style, isValid)
324
- if (styles.length > 0) {
325
- c4Globals.styles[globalStyleId] = styles as c4.NonEmptyArray<c4.ViewRuleStyle>
326
- }
327
- } catch (e) {
328
- logWarnError(e)
329
- }
330
- }
331
- }
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
-
370
- private parseGlobalStyleOrGroup(
371
- astRule: ast.GlobalStyle | ast.GlobalStyleGroup,
372
- isValid: IsValidFn
373
- ): c4.ViewRuleStyle[] {
374
- if (ast.isGlobalStyle(astRule)) {
375
- return [this.parseViewRuleStyle(astRule, isValid)]
376
- }
377
- if (ast.isGlobalStyleGroup(astRule)) {
378
- return astRule.styles.map(s => this.parseViewRuleStyle(s, isValid))
379
- }
380
- nonexhaustive(astRule)
381
- }
382
-
383
- private parseViews(doc: ParsedLikeC4LangiumDocument, isValid: IsValidFn) {
384
- const viewBlocks = doc.parseResult.value.views.filter(v => isValid(v))
385
- for (const viewBlock of viewBlocks) {
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
- })
394
-
395
- for (const view of viewBlock.views) {
396
- try {
397
- if (!isValid(view)) {
398
- continue
399
- }
400
- doc.c4Views.push(
401
- ast.isElementView(view)
402
- ? this.parseElementView(view, localStyles, isValid)
403
- : this.parseDynamicElementView(view, localStyles, isValid)
404
- )
405
- } catch (e) {
406
- logWarnError(e)
407
- }
408
- }
409
- }
410
- }
411
-
412
- // TODO validate view rules
413
- private parseViewRulePredicate(astNode: ast.ViewRulePredicate, _isValid: IsValidFn): c4.ViewRulePredicate {
414
- const exprs = [] as c4.Expression[]
415
- let predicate: ast.Predicates | undefined = astNode.predicates
416
- while (predicate) {
417
- try {
418
- if (isTruthy(predicate.value) && _isValid(predicate.value as any)) {
419
- exprs.unshift(this.parsePredicate(predicate.value, _isValid))
420
- }
421
- } catch (e) {
422
- logWarnError(e)
423
- }
424
- predicate = predicate.prev
425
- }
426
- return ast.isIncludePredicate(astNode) ? { include: exprs } : { exclude: exprs }
427
- }
428
-
429
- private parsePredicate(astNode: ast.Predicate, _isValid: IsValidFn): c4.Expression {
430
- if (ast.isElementPredicate(astNode)) {
431
- return this.parseElementPredicate(astNode, _isValid)
432
- }
433
- if (ast.isRelationPredicate(astNode)) {
434
- return this.parseRelationPredicate(astNode, _isValid)
435
- }
436
- nonexhaustive(astNode)
437
- }
438
-
439
- private parseElementExpressionsIterator(astNode: ast.ElementExpressionsIterator): c4.ElementExpression[] {
440
- const exprs = [] as c4.ElementExpression[]
441
- let iter: ast.ElementExpressionsIterator['prev'] = astNode
442
- while (iter) {
443
- try {
444
- exprs.unshift(this.parseElementExpr(iter.value))
445
- } catch (e) {
446
- logWarnError(e)
447
- }
448
- iter = iter.prev
449
- }
450
- return exprs
451
- }
452
-
453
- private parseElementPredicate(astNode: ast.ElementPredicate, _isValid: IsValidFn): c4.ElementPredicateExpression {
454
- if (ast.isElementPredicateWith(astNode)) {
455
- return this.parseElementPredicateWith(astNode, _isValid)
456
- }
457
- if (ast.isElementPredicateWhere(astNode)) {
458
- return this.parseElementPredicateWhere(astNode)
459
- }
460
- if (ast.isElementExpression(astNode)) {
461
- return this.parseElementExpr(astNode)
462
- }
463
- nonexhaustive(astNode)
464
- }
465
-
466
- private parseElementExpr(astNode: ast.ElementExpression): c4.ElementExpression {
467
- if (ast.isWildcardExpression(astNode)) {
468
- return {
469
- wildcard: true
470
- }
471
- }
472
- if (ast.isElementKindExpression(astNode)) {
473
- invariant(astNode.kind?.ref, 'ElementKindExpr kind is not resolved: ' + astNode.$cstNode?.text)
474
- return {
475
- elementKind: astNode.kind.ref.name as c4.ElementKind,
476
- isEqual: astNode.isEqual
477
- }
478
- }
479
- if (ast.isElementTagExpression(astNode)) {
480
- invariant(astNode.tag?.ref, 'ElementTagExpr tag is not resolved: ' + astNode.$cstNode?.text)
481
- let elementTag = astNode.tag.$refText
482
- if (elementTag.startsWith('#')) {
483
- elementTag = elementTag.slice(1)
484
- }
485
- return {
486
- elementTag: elementTag as c4.Tag,
487
- isEqual: astNode.isEqual
488
- }
489
- }
490
- if (ast.isExpandElementExpression(astNode)) {
491
- const elementNode = elementRef(astNode.expand)
492
- invariant(elementNode, 'Element not found ' + astNode.expand.$cstNode?.text)
493
- const expanded = this.resolveFqn(elementNode)
494
- return {
495
- expanded
496
- }
497
- }
498
- if (ast.isElementDescedantsExpression(astNode)) {
499
- const elementNode = elementRef(astNode.parent)
500
- invariant(elementNode, 'Element not found ' + astNode.parent.$cstNode?.text)
501
- const element = this.resolveFqn(elementNode)
502
- return {
503
- element,
504
- isDescedants: true
505
- }
506
- }
507
- if (ast.isElementRef(astNode)) {
508
- const elementNode = elementRef(astNode)
509
- invariant(elementNode, 'Element not found ' + astNode.$cstNode?.text)
510
- const element = this.resolveFqn(elementNode)
511
- return {
512
- element
513
- }
514
- }
515
- nonexhaustive(astNode)
516
- }
517
-
518
- private parseElementPredicateWith(
519
- astNode: ast.ElementPredicateWith,
520
- _isValid: IsValidFn
521
- ): c4.CustomElementExpr {
522
- const expr = this.parseElementPredicate(astNode.subject, _isValid)
523
- const props = astNode.custom?.props ?? []
524
- return props.reduce(
525
- (acc, prop) => {
526
- if (ast.isNavigateToProperty(prop)) {
527
- const viewId = prop.value.view.$refText
528
- if (isTruthy(viewId)) {
529
- acc.custom.navigateTo = viewId as c4.ViewID
530
- }
531
- return acc
532
- }
533
- if (ast.isElementStringProperty(prop)) {
534
- if (isDefined(prop.value)) {
535
- let value = prop.key === 'description' ? removeIndent(prop.value) : toSingleLine(prop.value)
536
- acc.custom[prop.key] = value || ''
537
- }
538
- return acc
539
- }
540
- if (ast.isIconProperty(prop)) {
541
- const value = prop.libicon?.ref?.name ?? prop.value
542
- if (isDefined(value)) {
543
- acc.custom[prop.key] = value as c4.IconUrl
544
- }
545
- return acc
546
- }
547
- if (ast.isColorProperty(prop)) {
548
- const value = toColor(prop)
549
- if (isDefined(value)) {
550
- acc.custom[prop.key] = value
551
- }
552
- return acc
553
- }
554
- if (ast.isShapeProperty(prop)) {
555
- if (isDefined(prop.value)) {
556
- acc.custom[prop.key] = prop.value
557
- }
558
- return acc
559
- }
560
- if (ast.isBorderProperty(prop)) {
561
- if (isDefined(prop.value)) {
562
- acc.custom[prop.key] = prop.value
563
- }
564
- return acc
565
- }
566
- if (ast.isOpacityProperty(prop)) {
567
- if (isDefined(prop.value)) {
568
- acc.custom[prop.key] = parseAstOpacityProperty(prop)
569
- }
570
- return acc
571
- }
572
- if (ast.isNotationProperty(prop)) {
573
- if (isTruthy(prop.value)) {
574
- acc.custom[prop.key] = removeIndent(prop.value)
575
- }
576
- return acc
577
- }
578
- nonexhaustive(prop)
579
- },
580
- {
581
- custom: {
582
- expr
583
- }
584
- } as c4.CustomElementExpr
585
- )
586
- }
587
- private parseElementPredicateWhere(
588
- astNode: ast.ElementPredicateWhere
589
- ): c4.ElementWhereExpr {
590
- const expr = this.parseElementExpr(astNode.subject)
591
- return {
592
- where: {
593
- expr,
594
- condition: astNode.where ? parseWhereClause(astNode.where) : {
595
- kind: { neq: '--always-true--' }
596
- }
597
- }
598
- }
599
- }
600
-
601
- private parseRelationPredicate(astNode: ast.RelationPredicate, _isValid: IsValidFn): c4.RelationPredicateExpression {
602
- if (ast.isRelationPredicateWith(astNode)) {
603
- let relation = ast.isRelationPredicateWhere(astNode.subject)
604
- ? this.parseRelationPredicateWhere(astNode.subject)
605
- : this.parseRelationExpr(astNode.subject)
606
-
607
- return this.parseRelationPredicateWith(astNode, relation)
608
- }
609
- if (ast.isRelationPredicateWhere(astNode)) {
610
- return this.parseRelationPredicateWhere(astNode)
611
- }
612
- if (ast.isRelationExpression(astNode)) {
613
- return this.parseRelationExpr(astNode)
614
- }
615
- nonexhaustive(astNode)
616
- }
617
-
618
- private parseRelationPredicateWhere(
619
- astNode: ast.RelationPredicateWhere
620
- ): c4.RelationWhereExpr {
621
- const expr = this.parseRelationExpr(astNode.subject)
622
- return {
623
- where: {
624
- expr,
625
- condition: astNode.where ? parseWhereClause(astNode.where) : {
626
- kind: { neq: '--always-true--' }
627
- }
628
- }
629
- }
630
- }
631
-
632
- private parseRelationPredicateWith(
633
- astNode: ast.RelationPredicateWith,
634
- relation: c4.RelationExpression | c4.RelationWhereExpr
635
- ): c4.CustomRelationExpr {
636
- const props = astNode.custom?.props ?? []
637
- return props.reduce(
638
- (acc, prop) => {
639
- if (ast.isRelationStringProperty(prop) || ast.isNotationProperty(prop) || ast.isNotesProperty(prop)) {
640
- if (isDefined(prop.value)) {
641
- acc.customRelation[prop.key] = removeIndent(prop.value) ?? ''
642
- }
643
- return acc
644
- }
645
- if (ast.isArrowProperty(prop)) {
646
- if (isTruthy(prop.value)) {
647
- acc.customRelation[prop.key] = prop.value
648
- }
649
- return acc
650
- }
651
- if (ast.isColorProperty(prop)) {
652
- const value = toColor(prop)
653
- if (isTruthy(value)) {
654
- acc.customRelation[prop.key] = value
655
- }
656
- return acc
657
- }
658
- if (ast.isLineProperty(prop)) {
659
- if (isTruthy(prop.value)) {
660
- acc.customRelation[prop.key] = prop.value
661
- }
662
- return acc
663
- }
664
- if (ast.isRelationNavigateToProperty(prop)) {
665
- const viewId = prop.value.view.ref?.name
666
- if (isTruthy(viewId)) {
667
- acc.customRelation.navigateTo = viewId as c4.ViewID
668
- }
669
- return acc
670
- }
671
- nonexhaustive(prop)
672
- },
673
- {
674
- customRelation: {
675
- relation
676
- }
677
- } as c4.CustomRelationExpr
678
- )
679
- }
680
-
681
- private parseRelationExpr(astNode: ast.RelationExpression): c4.RelationExpression {
682
- if (ast.isDirectedRelationExpression(astNode)) {
683
- return {
684
- source: this.parseElementExpr(astNode.source.from),
685
- target: this.parseElementExpr(astNode.target),
686
- isBidirectional: astNode.source.isBidirectional
687
- }
688
- }
689
- if (ast.isInOutRelationExpression(astNode)) {
690
- return {
691
- inout: this.parseElementExpr(astNode.inout.to)
692
- }
693
- }
694
- if (ast.isOutgoingRelationExpression(astNode)) {
695
- return {
696
- outgoing: this.parseElementExpr(astNode.from)
697
- }
698
- }
699
- if (ast.isIncomingRelationExpression(astNode)) {
700
- return {
701
- incoming: this.parseElementExpr(astNode.to)
702
- }
703
- }
704
- nonexhaustive(astNode)
705
- }
706
-
707
- private parseViewRule(astRule: ast.ViewRule, isValid: IsValidFn): c4.ViewRule {
708
- if (ast.isViewRulePredicate(astRule)) {
709
- return this.parseViewRulePredicate(astRule, isValid)
710
- }
711
- if (ast.isViewRuleGlobalPredicateRef(astRule)) {
712
- return this.parseViewRuleGlobalPredicateRef(astRule, isValid)
713
- }
714
- if (ast.isViewRuleStyleOrGlobalRef(astRule)) {
715
- return this.parseViewRuleStyleOrGlobalRef(astRule, isValid)
716
- }
717
- if (ast.isViewRuleAutoLayout(astRule)) {
718
- return toAutoLayout(astRule)
719
- }
720
- if (ast.isViewRuleGroup(astRule)) {
721
- return this.parseViewRuleGroup(astRule, isValid)
722
- }
723
- nonexhaustive(astRule)
724
- }
725
-
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
- }
36
+ private cachedParsers = new DefaultWeakMap<LangiumDocument, DocumentParser>((doc: LangiumDocument) =>
37
+ new DocumentParser(this.services, doc as ParsedLikeC4LangiumDocument)
38
+ )
734
39
 
735
- private parseViewRuleStyleOrGlobalRef(
736
- astRule: ast.ViewRuleStyleOrGlobalRef,
737
- isValid: IsValidFn
738
- ): c4.ViewRuleStyleOrGlobalRef {
739
- if (ast.isViewRuleStyle(astRule)) {
740
- return this.parseViewRuleStyle(astRule, isValid)
741
- }
742
- if (ast.isViewRuleGlobalStyle(astRule)) {
743
- return this.parseViewRuleGlobalStyle(astRule, isValid)
744
- }
745
- nonexhaustive(astRule)
746
- }
747
-
748
- private parseViewRuleStyle(astRule: ast.ViewRuleStyle | ast.GlobalStyle, isValid: IsValidFn): c4.ViewRuleStyle {
749
- const styleProps = astRule.props.filter(ast.isStyleProperty)
750
- const targets = astRule.target
751
- const notation = astRule.props.find(ast.isNotationProperty)
752
- return this.parseRuleStyle(styleProps, targets, isValid, notation)
753
- }
754
-
755
- private parseViewRuleGroup(astNode: ast.ViewRuleGroup, _isValid: IsValidFn): c4.ViewRuleGroup {
756
- const groupRules = [] as c4.ViewRuleGroup['groupRules']
757
- for (const rule of astNode.groupRules) {
758
- try {
759
- if (!_isValid(rule)) {
760
- continue
761
- }
762
- if (ast.isViewRulePredicate(rule)) {
763
- groupRules.push(this.parseViewRulePredicate(rule, _isValid))
764
- continue
765
- }
766
- if (ast.isViewRuleGroup(rule)) {
767
- groupRules.push(this.parseViewRuleGroup(rule, _isValid))
768
- continue
769
- }
770
- nonexhaustive(rule)
771
- } catch (e) {
772
- logWarnError(e)
773
- }
774
- }
775
- return {
776
- title: toSingleLine(astNode.title) ?? null,
777
- groupRules,
778
- ...toElementStyle(astNode.props, _isValid)
779
- }
780
- }
781
-
782
- private parseRuleStyle(
783
- styleProperties: ast.StyleProperty[],
784
- elementExpressionsIterator: ast.ElementExpressionsIterator,
785
- isValid: IsValidFn,
786
- notationProperty?: NotationProperty
787
- ): c4.ViewRuleStyle {
788
- const styleProps = toElementStyle(styleProperties, isValid)
789
- const notation = removeIndent(notationProperty?.value)
790
- const targets = this.parseElementExpressionsIterator(elementExpressionsIterator)
791
- return {
792
- targets,
793
- ...(notation && { notation }),
794
- style: {
795
- ...styleProps
796
- }
797
- }
798
- }
799
-
800
- private parseViewRuleGlobalStyle(astRule: ast.ViewRuleGlobalStyle, _isValid: IsValidFn): c4.ViewRuleGlobalStyle {
801
- return {
802
- styleId: astRule.style.$refText as c4.GlobalStyleID
803
- }
40
+ constructor(private services: LikeC4Services) {
804
41
  }
805
42
 
806
- private parseViewManualLaout(node: ast.DynamicView | ast.ElementView): c4.ViewManualLayout | undefined {
807
- const commentNode = CstUtils.findCommentNode(node.$cstNode, ['BLOCK_COMMENT'])
808
- if (!commentNode || !hasManualLayout(commentNode.text)) {
809
- return undefined
810
- }
43
+ parse(doc: LangiumDocument): ParsedLikeC4LangiumDocument {
44
+ invariant(isFqnIndexedDocument(doc), `Not a FqnIndexedDocument: ${doc.uri.toString(true)}`)
811
45
  try {
812
- return deserializeFromComment(commentNode.text)
813
- } catch (e) {
814
- const doc = getDocument(node)
815
- logger.warn(e)
816
- logger.warn(
817
- `Ignoring manual layout of "${node.name ?? 'unnamed'}" at ${doc.uri.fsPath}:${
818
- 1 + (commentNode.range.start.line || 0)
819
- }`
820
- )
821
- return undefined
822
- }
823
- }
824
-
825
- private parseDynamicParallelSteps(node: ast.DynamicViewParallelSteps): c4.DynamicViewParallelSteps {
826
- return {
827
- __parallel: node.steps.map(step => this.parseDynamicStep(step))
828
- }
829
- }
830
-
831
- private parseDynamicStep(node: ast.DynamicViewStep): c4.DynamicViewStep {
832
- const sourceEl = elementRef(node.source)
833
- if (!sourceEl) {
834
- throw new Error('Invalid reference to source')
835
- }
836
- const targetEl = elementRef(node.target)
837
- if (!targetEl) {
838
- throw new Error('Invalid reference to target')
839
- }
840
- let source = this.resolveFqn(sourceEl)
841
- let target = this.resolveFqn(targetEl)
842
- const title = removeIndent(node.title) ?? null
843
-
844
- let step: Writable<c4.DynamicViewStep> = {
845
- source,
846
- target,
847
- title
848
- }
849
- if (node.isBackward) {
850
- step = {
851
- source: target,
852
- target: source,
853
- title,
854
- isBackward: true
855
- }
856
- }
857
- if (Array.isArray(node.custom?.props)) {
858
- for (const prop of node.custom.props) {
859
- try {
860
- if (ast.isRelationStringProperty(prop) || ast.isNotationProperty(prop) || ast.isNotesProperty(prop)) {
861
- if (isDefined(prop.value)) {
862
- step[prop.key] = removeIndent(prop.value) ?? ''
863
- }
864
- continue
865
- }
866
- if (ast.isArrowProperty(prop)) {
867
- if (isDefined(prop.value)) {
868
- step[prop.key] = prop.value
869
- }
870
- continue
871
- }
872
- if (ast.isColorProperty(prop)) {
873
- const value = toColor(prop)
874
- if (isDefined(value)) {
875
- step[prop.key] = value
876
- }
877
- continue
878
- }
879
- if (ast.isLineProperty(prop)) {
880
- if (isDefined(prop.value)) {
881
- step[prop.key] = prop.value
882
- }
883
- continue
884
- }
885
- if (ast.isRelationNavigateToProperty(prop)) {
886
- const viewId = prop.value.view.ref?.name
887
- if (isTruthy(viewId)) {
888
- step.navigateTo = viewId as c4.ViewID
889
- }
890
- continue
891
- }
892
- nonexhaustive(prop)
893
- }
894
- catch (e) {
895
- logWarnError(e)
896
- }
897
- }
898
- }
899
- return step
900
- }
901
-
902
- private parseElementView(
903
- astNode: ast.ElementView,
904
- additionalStyles: c4.ViewRuleStyleOrGlobalRef[],
905
- isValid: IsValidFn
906
- ): ParsedAstElementView {
907
- const body = astNode.body
908
- invariant(body, 'ElementView body is not defined')
909
- const astPath = this.getAstNodePath(astNode)
910
-
911
- let viewOf = null as c4.Fqn | null
912
- if ('viewOf' in astNode) {
913
- const viewOfEl = elementRef(astNode.viewOf)
914
- const _viewOf = viewOfEl && this.resolveFqn(viewOfEl)
915
- if (!_viewOf) {
916
- logger.warn('viewOf is not resolved: ' + astNode.$cstNode?.text)
917
- } else {
918
- viewOf = _viewOf
919
- }
920
- }
921
-
922
- let id = astNode.name
923
- if (!id) {
924
- id = 'view_' + stringHash(
925
- getDocument(astNode).uri.toString(),
926
- astPath,
927
- viewOf ?? ''
928
- ) as c4.ViewID
929
- }
930
-
931
- const title = toSingleLine(body.props.find(p => p.key === 'title')?.value) ?? null
932
- const description = removeIndent(body.props.find(p => p.key === 'description')?.value) ?? null
933
-
934
- const tags = this.convertTags(body)
935
- const links = this.convertLinks(body)
936
-
937
- const manualLayout = this.parseViewManualLaout(astNode)
938
-
939
- const view: ParsedAstElementView = {
940
- __: 'element',
941
- id: id as c4.ViewID,
942
- astPath,
943
- title,
944
- description,
945
- tags,
946
- links: isNonEmptyArray(links) ? links : null,
947
- rules: [
948
- ...additionalStyles,
949
- ...body.rules.flatMap(n => {
950
- try {
951
- return isValid(n) ? this.parseViewRule(n, isValid) : []
952
- } catch (e) {
953
- logWarnError(e)
954
- return []
955
- }
956
- })
957
- ],
958
- ...(viewOf && { viewOf }),
959
- ...(manualLayout && { manualLayout })
960
- }
961
- ViewOps.writeId(astNode, view.id)
962
-
963
- if ('extends' in astNode) {
964
- const extendsView = astNode.extends.view.ref
965
- invariant(extendsView?.name, 'view extends is not resolved: ' + astNode.$cstNode?.text)
966
- return Object.assign(view, {
967
- extends: extendsView.name as c4.ViewID
968
- })
969
- }
970
-
971
- return view
972
- }
973
-
974
- private parseDynamicElementView(
975
- astNode: ast.DynamicView,
976
- additionalStyles: c4.ViewRuleStyleOrGlobalRef[],
977
- isValid: IsValidFn
978
- ): ParsedAstDynamicView {
979
- const body = astNode.body
980
- invariant(body, 'DynamicElementView body is not defined')
981
- // only valid props
982
- const props = body.props.filter(isValid)
983
- const astPath = this.getAstNodePath(astNode)
984
-
985
- let id = astNode.name
986
- if (!id) {
987
- id = 'dynamic_' + stringHash(
988
- getDocument(astNode).uri.toString(),
989
- astPath
990
- ) as c4.ViewID
991
- }
992
-
993
- const title = toSingleLine(props.find(p => p.key === 'title')?.value) ?? null
994
- const description = removeIndent(props.find(p => p.key === 'description')?.value) ?? null
995
-
996
- const tags = this.convertTags(body)
997
- const links = this.convertLinks(body)
998
-
999
- ViewOps.writeId(astNode, id as c4.ViewID)
1000
-
1001
- const manualLayout = this.parseViewManualLaout(astNode)
1002
-
1003
- return {
1004
- __: 'dynamic',
1005
- id: id as c4.ViewID,
1006
- astPath,
1007
- title,
1008
- description,
1009
- tags,
1010
- links: isNonEmptyArray(links) ? links : null,
1011
- rules: [
1012
- ...additionalStyles,
1013
- ...body.rules.flatMap(n => {
1014
- try {
1015
- return isValid(n) ? this.parseDynamicViewRule(n, isValid) : []
1016
- } catch (e) {
1017
- logWarnError(e)
1018
- return []
1019
- }
1020
- }, [] as Array<c4.DynamicViewRule>)
1021
- ],
1022
- steps: body.steps.reduce((acc, n) => {
1023
- try {
1024
- if (isValid(n)) {
1025
- if (ast.isDynamicViewParallelSteps(n)) {
1026
- acc.push(this.parseDynamicParallelSteps(n))
1027
- } else {
1028
- acc.push(this.parseDynamicStep(n))
1029
- }
1030
- }
1031
- } catch (e) {
1032
- logWarnError(e)
1033
- }
1034
- return acc
1035
- }, [] as c4.DynamicViewStepOrParallel[]),
1036
- ...(manualLayout && { manualLayout })
1037
- }
1038
- }
1039
-
1040
- private parseDynamicViewRule(astRule: ast.DynamicViewRule, isValid: IsValidFn): c4.DynamicViewRule {
1041
- if (ast.isDynamicViewIncludePredicate(astRule)) {
1042
- return this.parseDynamicViewIncludePredicate(astRule, isValid)
1043
- }
1044
- if (ast.isDynamicViewGlobalPredicateRef(astRule)) {
1045
- return this.parseViewRuleGlobalPredicateRef(astRule, isValid)
1046
- }
1047
- if (ast.isViewRuleStyleOrGlobalRef(astRule)) {
1048
- return this.parseViewRuleStyleOrGlobalRef(astRule, isValid)
1049
- }
1050
- if (ast.isViewRuleAutoLayout(astRule)) {
1051
- return toAutoLayout(astRule)
1052
- }
1053
- nonexhaustive(astRule)
1054
- }
1055
-
1056
- private parseDynamicViewIncludePredicate(
1057
- astRule: ast.DynamicViewIncludePredicate,
1058
- isValid: IsValidFn
1059
- ): c4.DynamicViewIncludeRule {
1060
- const include = [] as c4.ElementPredicateExpression[]
1061
- let iter: ast.DynamicViewPredicateIterator | undefined = astRule.predicates
1062
- while (iter) {
1063
- try {
1064
- if (isValid(iter.value as any)) {
1065
- const c4expr = this.parseElementPredicate(iter.value, isValid)
1066
- include.unshift(c4expr)
1067
- }
1068
- } catch (e) {
1069
- logWarnError(e)
1070
- }
1071
- iter = iter.prev
1072
- }
1073
- return { include }
1074
- }
1075
-
1076
- protected resolveFqn(node: ast.Element | ast.ExtendElement) {
1077
- if (ast.isExtendElement(node)) {
1078
- return getFqnElementRef(node.element)
1079
- }
1080
- const fqn = this.fqnIndex.getFqn(node)
1081
- invariant(fqn, `Not indexed element: ${this.getAstNodePath(node)}`)
1082
- return fqn
1083
- }
1084
-
1085
- private getAstNodePath(node: AstNode) {
1086
- return this.services.workspace.AstNodeLocator.getAstNodePath(node)
1087
- }
1088
-
1089
- private getMetadata(metadataAstNode: ast.MetadataProperty | undefined): { [key: string]: string } | undefined {
1090
- return metadataAstNode?.props != null
1091
- ? mapToObj(metadataAstNode.props, (p) => [p.key, removeIndent(p.value)])
1092
- : undefined
1093
- }
1094
-
1095
- private convertTags<E extends { tags?: ast.Tags }>(withTags?: E) {
1096
- let iter = withTags?.tags
1097
- if (!iter) {
1098
- return null
1099
- }
1100
- const tags = [] as c4.Tag[]
1101
- while (iter) {
1102
- try {
1103
- const values = iter.values.map(t => t.ref?.name).filter(isTruthy) as c4.Tag[]
1104
- if (values.length > 0) {
1105
- tags.unshift(...values)
1106
- }
1107
- } catch (e) {
1108
- // ignore
1109
- }
1110
- iter = iter.prev
1111
- }
1112
- return isNonEmptyArray(tags) ? tags : null
1113
- }
1114
-
1115
- private convertLinks(source?: ast.LinkProperty['$container']): ParsedLink[] | undefined {
1116
- if (!source?.props || source.props.length === 0) {
1117
- return undefined
1118
- }
1119
- return pipe(
1120
- source.props,
1121
- filter(ast.isLinkProperty),
1122
- flatMap(p => {
1123
- const url = p.value
1124
- if (isTruthy(url)) {
1125
- const title = isTruthy(p.title) ? toSingleLine(p.title) : undefined
1126
- return title ? { url, title } : { url }
1127
- }
1128
- return []
1129
- })
1130
- )
46
+ const props: Required<Omit<LikeC4DocumentProps, 'c4fqnIndex' | 'diagnostics'>> = {
47
+ c4Specification: {
48
+ tags: new Set(),
49
+ elements: {},
50
+ relationships: {},
51
+ colors: {},
52
+ deployments: {}
53
+ },
54
+ c4Elements: [],
55
+ c4Relations: [],
56
+ c4Deployments: [],
57
+ c4DeploymentRelations: [],
58
+ c4Globals: {
59
+ predicates: {},
60
+ dynamicPredicates: {},
61
+ styles: {}
62
+ },
63
+ c4Views: []
64
+ }
65
+ doc = Object.assign(doc, props)
66
+ const parser = this.cachedParsers.get(doc)
67
+ parser.parseSpecification()
68
+ parser.parseModel()
69
+ parser.parseGlobals()
70
+ parser.parseDeployment()
71
+ parser.parseViews()
72
+ return parser.doc
73
+ } catch (cause) {
74
+ throw new Error(`Error parsing document ${doc.uri.toString()}`, { cause })
75
+ }
76
+ }
77
+
78
+ forDocument(doc: LangiumDocument): DocumentParser {
79
+ invariant(isFqnIndexedDocument(doc), `Not a FqnIndexedDocument: ${doc.uri.toString(true)}`)
80
+ return this.cachedParsers.get(doc)
1131
81
  }
1132
82
  }