@likec4/language-server 1.18.0 → 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 (220) hide show
  1. package/dist/LikeC4FileSystem.d.ts +13 -0
  2. package/dist/LikeC4FileSystem.js +27 -0
  3. package/dist/Rpc.d.ts +9 -0
  4. package/dist/Rpc.js +126 -0
  5. package/dist/ast.d.ts +200 -0
  6. package/dist/ast.js +276 -0
  7. package/dist/browser.d.ts +6 -20
  8. package/dist/browser.js +13 -0
  9. package/dist/formatting/LikeC4Formatter.d.ts +27 -0
  10. package/dist/formatting/LikeC4Formatter.js +261 -0
  11. package/dist/formatting/utils.d.ts +6 -0
  12. package/dist/formatting/utils.js +15 -0
  13. package/dist/generated/ast.d.ts +1242 -0
  14. package/dist/generated/ast.js +1945 -0
  15. package/dist/generated/grammar.d.ts +6 -0
  16. package/dist/generated/grammar.js +3 -0
  17. package/dist/generated/module.d.ts +9 -0
  18. package/dist/generated/module.js +23 -0
  19. package/dist/generated-lib/icons.d.ts +1 -0
  20. package/dist/{likec4lib.mjs → generated-lib/icons.js} +1 -6
  21. package/dist/index.d.ts +8 -31
  22. package/dist/index.js +13 -0
  23. package/dist/like-c4.langium +845 -0
  24. package/dist/likec4lib.d.ts +4 -6
  25. package/dist/likec4lib.js +4 -0
  26. package/dist/logger.d.ts +7 -0
  27. package/dist/logger.js +73 -0
  28. package/dist/lsp/CodeLensProvider.d.ts +9 -0
  29. package/dist/lsp/CodeLensProvider.js +40 -0
  30. package/dist/lsp/CompletionProvider.d.ts +6 -0
  31. package/dist/lsp/CompletionProvider.js +135 -0
  32. package/dist/lsp/DocumentHighlightProvider.d.ts +9 -0
  33. package/dist/lsp/DocumentHighlightProvider.js +10 -0
  34. package/dist/lsp/DocumentLinkProvider.d.ts +11 -0
  35. package/dist/lsp/DocumentLinkProvider.js +49 -0
  36. package/dist/lsp/DocumentSymbolProvider.d.ts +23 -0
  37. package/dist/lsp/DocumentSymbolProvider.js +202 -0
  38. package/dist/lsp/HoverProvider.d.ts +10 -0
  39. package/dist/lsp/HoverProvider.js +69 -0
  40. package/dist/lsp/RenameProvider.d.ts +5 -0
  41. package/dist/lsp/RenameProvider.js +6 -0
  42. package/dist/lsp/SemanticTokenProvider.d.ts +7 -0
  43. package/dist/lsp/SemanticTokenProvider.js +297 -0
  44. package/dist/lsp/index.d.ts +7 -0
  45. package/dist/lsp/index.js +7 -0
  46. package/dist/model/deployments-index.d.ts +60 -0
  47. package/dist/model/deployments-index.js +181 -0
  48. package/dist/model/fqn-computation.d.ts +3 -0
  49. package/dist/model/fqn-computation.js +72 -0
  50. package/dist/model/fqn-index.d.ts +25 -0
  51. package/dist/model/fqn-index.js +96 -0
  52. package/dist/model/index.d.ts +6 -0
  53. package/dist/model/index.js +6 -0
  54. package/dist/model/model-builder.d.ts +32 -0
  55. package/dist/model/model-builder.js +598 -0
  56. package/dist/model/model-locator.d.ts +23 -0
  57. package/dist/model/model-locator.js +126 -0
  58. package/dist/model/model-parser-where.d.ts +3 -0
  59. package/dist/model/model-parser-where.js +70 -0
  60. package/dist/model/model-parser.d.ts +292 -0
  61. package/dist/model/model-parser.js +72 -0
  62. package/dist/model/parser/Base.d.ts +28 -0
  63. package/dist/model/parser/Base.js +87 -0
  64. package/dist/model/parser/DeploymentModelParser.d.ts +33 -0
  65. package/dist/model/parser/DeploymentModelParser.js +162 -0
  66. package/dist/model/parser/DeploymentViewParser.d.ts +38 -0
  67. package/dist/model/parser/DeploymentViewParser.js +98 -0
  68. package/dist/model/parser/FqnRefParser.d.ts +29 -0
  69. package/dist/model/parser/FqnRefParser.js +108 -0
  70. package/dist/model/parser/GlobalsParser.d.ts +66 -0
  71. package/dist/model/parser/GlobalsParser.js +80 -0
  72. package/dist/model/parser/ModelParser.d.ts +27 -0
  73. package/dist/model/parser/ModelParser.js +122 -0
  74. package/dist/model/parser/PredicatesParser.d.ts +34 -0
  75. package/dist/model/parser/PredicatesParser.js +272 -0
  76. package/dist/model/parser/SpecificationParser.d.ts +27 -0
  77. package/dist/model/parser/SpecificationParser.js +120 -0
  78. package/dist/model/parser/ViewsParser.d.ts +64 -0
  79. package/dist/model/parser/ViewsParser.js +377 -0
  80. package/dist/model-change/ModelChanges.d.ts +15 -0
  81. package/dist/model-change/ModelChanges.js +89 -0
  82. package/dist/model-change/changeElementStyle.d.ts +16 -0
  83. package/dist/model-change/changeElementStyle.js +136 -0
  84. package/dist/model-change/changeViewLayout.d.ts +12 -0
  85. package/dist/model-change/changeViewLayout.js +32 -0
  86. package/dist/model-change/saveManualLayout.d.ts +11 -0
  87. package/dist/model-change/saveManualLayout.js +27 -0
  88. package/dist/module.d.ts +62 -0
  89. package/dist/module.js +123 -0
  90. package/dist/protocol.d.ts +20 -23
  91. package/dist/protocol.js +14 -0
  92. package/dist/references/index.d.ts +3 -0
  93. package/dist/references/index.js +3 -0
  94. package/dist/references/name-provider.d.ts +9 -0
  95. package/dist/references/name-provider.js +33 -0
  96. package/dist/references/scope-computation.d.ts +20 -0
  97. package/dist/references/scope-computation.js +281 -0
  98. package/dist/references/scope-provider.d.ts +16 -0
  99. package/dist/references/scope-provider.js +165 -0
  100. package/dist/shared/NodeKindProvider.d.ts +15 -0
  101. package/dist/shared/NodeKindProvider.js +108 -0
  102. package/dist/shared/WorkspaceManager.d.ts +18 -0
  103. package/dist/shared/WorkspaceManager.js +36 -0
  104. package/dist/shared/WorkspaceSymbolProvider.d.ts +3 -0
  105. package/dist/shared/WorkspaceSymbolProvider.js +3 -0
  106. package/dist/shared/index.d.ts +3 -0
  107. package/dist/shared/index.js +3 -0
  108. package/dist/test/index.d.ts +1 -0
  109. package/dist/test/index.js +1 -0
  110. package/dist/test/setup.d.ts +1 -0
  111. package/dist/test/setup.js +7 -0
  112. package/dist/test/testServices.d.ts +22 -0
  113. package/dist/test/testServices.js +119 -0
  114. package/dist/utils/elementRef.d.ts +11 -0
  115. package/dist/utils/elementRef.js +15 -0
  116. package/dist/utils/fqnRef.d.ts +8 -0
  117. package/dist/utils/fqnRef.js +46 -0
  118. package/dist/utils/index.d.ts +1 -0
  119. package/dist/utils/index.js +1 -0
  120. package/dist/utils/printDocs.d.ts +2 -0
  121. package/dist/utils/printDocs.js +1 -0
  122. package/dist/utils/stringHash.d.ts +1 -0
  123. package/dist/utils/stringHash.js +5 -0
  124. package/dist/validation/_shared.d.ts +3 -0
  125. package/dist/validation/_shared.js +22 -0
  126. package/dist/validation/deployment-checks.d.ts +6 -0
  127. package/dist/validation/deployment-checks.js +114 -0
  128. package/dist/validation/dynamic-view-rule.d.ts +4 -0
  129. package/dist/validation/dynamic-view-rule.js +16 -0
  130. package/dist/validation/dynamic-view-step.d.ts +4 -0
  131. package/dist/validation/dynamic-view-step.js +33 -0
  132. package/dist/validation/element.d.ts +4 -0
  133. package/dist/validation/element.js +49 -0
  134. package/dist/validation/index.d.ts +15 -0
  135. package/dist/validation/index.js +152 -0
  136. package/dist/validation/property-checks.d.ts +6 -0
  137. package/dist/validation/property-checks.js +38 -0
  138. package/dist/validation/relation.d.ts +5 -0
  139. package/dist/validation/relation.js +56 -0
  140. package/dist/validation/specification.d.ts +11 -0
  141. package/dist/validation/specification.js +136 -0
  142. package/dist/validation/view-predicates/element-with.d.ts +4 -0
  143. package/dist/validation/view-predicates/element-with.js +30 -0
  144. package/dist/validation/view-predicates/expanded-element.d.ts +4 -0
  145. package/dist/validation/view-predicates/expanded-element.js +11 -0
  146. package/dist/validation/view-predicates/expression-v2.d.ts +5 -0
  147. package/dist/validation/view-predicates/expression-v2.js +83 -0
  148. package/dist/validation/view-predicates/incoming.d.ts +4 -0
  149. package/dist/validation/view-predicates/incoming.js +15 -0
  150. package/dist/validation/view-predicates/index.d.ts +6 -0
  151. package/dist/validation/view-predicates/index.js +6 -0
  152. package/dist/validation/view-predicates/outgoing.d.ts +4 -0
  153. package/dist/validation/view-predicates/outgoing.js +15 -0
  154. package/dist/validation/view-predicates/relation-with.d.ts +4 -0
  155. package/dist/validation/view-predicates/relation-with.js +12 -0
  156. package/dist/validation/view.d.ts +4 -0
  157. package/dist/validation/view.js +23 -0
  158. package/dist/view-utils/assignNavigateTo.d.ts +2 -0
  159. package/dist/view-utils/assignNavigateTo.js +25 -0
  160. package/dist/view-utils/index.d.ts +2 -0
  161. package/dist/view-utils/index.js +2 -0
  162. package/dist/view-utils/manual-layout.d.ts +7 -0
  163. package/dist/view-utils/manual-layout.js +99 -0
  164. package/dist/view-utils/resolve-relative-paths.d.ts +2 -0
  165. package/dist/view-utils/resolve-relative-paths.js +78 -0
  166. package/package.json +36 -53
  167. package/src/LikeC4FileSystem.ts +22 -21
  168. package/src/ast.ts +44 -133
  169. package/src/browser.ts +10 -11
  170. package/src/generated/ast.ts +177 -177
  171. package/src/generated/grammar.ts +1 -1
  172. package/src/index.ts +10 -8
  173. package/src/like-c4.langium +37 -34
  174. package/src/logger.ts +34 -55
  175. package/src/lsp/CompletionProvider.ts +4 -4
  176. package/src/lsp/HoverProvider.ts +5 -3
  177. package/src/lsp/SemanticTokenProvider.ts +2 -2
  178. package/src/model/deployments-index.ts +18 -14
  179. package/src/model/model-builder.ts +10 -8
  180. package/src/model/model-parser.ts +62 -1574
  181. package/src/model/parser/Base.ts +107 -0
  182. package/src/model/parser/DeploymentModelParser.ts +192 -0
  183. package/src/model/parser/DeploymentViewParser.ts +116 -0
  184. package/src/model/parser/FqnRefParser.ts +118 -0
  185. package/src/model/parser/GlobalsParser.ts +96 -0
  186. package/src/model/parser/ModelParser.ts +141 -0
  187. package/src/model/parser/PredicatesParser.ts +291 -0
  188. package/src/model/parser/SpecificationParser.ts +133 -0
  189. package/src/model/parser/ViewsParser.ts +428 -0
  190. package/src/module.ts +17 -18
  191. package/src/references/scope-provider.ts +13 -7
  192. package/src/utils/{deploymentRef.ts → fqnRef.ts} +27 -2
  193. package/src/validation/_shared.ts +0 -1
  194. package/src/validation/deployment-checks.ts +49 -62
  195. package/src/validation/index.ts +100 -9
  196. package/src/validation/view-predicates/expression-v2.ts +101 -0
  197. package/src/validation/view-predicates/index.ts +1 -1
  198. package/src/view-utils/assignNavigateTo.ts +1 -1
  199. package/src/view-utils/manual-layout.ts +25 -0
  200. package/dist/browser.cjs +0 -25
  201. package/dist/browser.d.cts +0 -23
  202. package/dist/browser.d.mts +0 -23
  203. package/dist/browser.mjs +0 -20
  204. package/dist/index.cjs +0 -53
  205. package/dist/index.d.cts +0 -34
  206. package/dist/index.d.mts +0 -34
  207. package/dist/index.mjs +0 -46
  208. package/dist/likec4lib.cjs +0 -1546
  209. package/dist/likec4lib.d.cts +0 -6
  210. package/dist/likec4lib.d.mts +0 -6
  211. package/dist/protocol.cjs +0 -25
  212. package/dist/protocol.d.cts +0 -48
  213. package/dist/protocol.d.mts +0 -48
  214. package/dist/protocol.mjs +0 -17
  215. package/dist/shared/language-server.CO_nmHiL.cjs +0 -7689
  216. package/dist/shared/language-server.Da6ey08o.d.cts +0 -1619
  217. package/dist/shared/language-server.De7S3e5Z.d.ts +0 -1619
  218. package/dist/shared/language-server.Dj4iDjtB.d.mts +0 -1619
  219. package/dist/shared/language-server.oO_9JoAG.mjs +0 -7666
  220. package/src/validation/view-predicates/deployments.ts +0 -56
@@ -0,0 +1,141 @@
1
+ import type * as c4 from '@likec4/core'
2
+ import { isNonEmptyArray, nonexhaustive, nonNullable } from '@likec4/core'
3
+ import { filter, first, isNonNullish, isTruthy, map, mapToObj, pipe } from 'remeda'
4
+ import {
5
+ type ParsedAstElement,
6
+ type ParsedAstRelation,
7
+ ast,
8
+ resolveRelationPoints,
9
+ streamModel,
10
+ toElementStyle,
11
+ toRelationshipStyleExcludeDefaults,
12
+ } from '../../ast'
13
+ import { logWarnError } from '../../logger'
14
+ import { stringHash } from '../../utils/stringHash'
15
+ import { type Base, removeIndent, toSingleLine } from './Base'
16
+
17
+ export type WithModel = ReturnType<typeof ModelParser>
18
+
19
+ export function ModelParser<TBase extends Base>(B: TBase) {
20
+ return class ModelParser extends B {
21
+ parseModel() {
22
+ const doc = this.doc
23
+ for (const el of streamModel(doc)) {
24
+ try {
25
+ if (ast.isElement(el)) {
26
+ doc.c4Elements.push(this.parseElement(el))
27
+ continue
28
+ }
29
+ if (ast.isRelation(el)) {
30
+ if (this.isValid(el)) {
31
+ doc.c4Relations.push(this.parseRelation(el))
32
+ }
33
+ continue
34
+ }
35
+ nonexhaustive(el)
36
+ } catch (e) {
37
+ logWarnError(e)
38
+ }
39
+ }
40
+ }
41
+
42
+ parseElement(astNode: ast.Element): ParsedAstElement {
43
+ const isValid = this.isValid
44
+ const id = this.resolveFqn(astNode)
45
+ const kind = nonNullable(astNode.kind.ref, 'Element kind is not resolved').name as c4.ElementKind
46
+ const tags = this.parseTags(astNode.body)
47
+ const stylePropsAst = astNode.body?.props.find(ast.isElementStyleProperty)?.props
48
+ const style = toElementStyle(stylePropsAst, isValid)
49
+ const metadata = this.getMetadata(astNode.body?.props.find(ast.isMetadataProperty))
50
+ const astPath = this.getAstNodePath(astNode)
51
+
52
+ let [title, description, technology] = astNode.props ?? []
53
+
54
+ const bodyProps = pipe(
55
+ astNode.body?.props ?? [],
56
+ filter(isValid),
57
+ filter(ast.isElementStringProperty),
58
+ mapToObj(p => [p.key, p.value || undefined]),
59
+ )
60
+
61
+ title = removeIndent(title ?? bodyProps.title)
62
+ description = removeIndent(bodyProps.description ?? description)
63
+ technology = toSingleLine(bodyProps.technology ?? technology)
64
+
65
+ const links = this.parseLinks(astNode.body)
66
+
67
+ // Property has higher priority than from style
68
+ const iconProp = astNode.body?.props.find(ast.isIconProperty)
69
+ if (iconProp && isValid(iconProp)) {
70
+ const value = iconProp.libicon?.ref?.name ?? iconProp.value
71
+ if (isTruthy(value)) {
72
+ style.icon = value as c4.IconUrl
73
+ }
74
+ }
75
+
76
+ return {
77
+ id,
78
+ kind,
79
+ astPath,
80
+ title: title ?? astNode.name,
81
+ ...(metadata && { metadata }),
82
+ ...(tags && { tags }),
83
+ ...(links && isNonEmptyArray(links) && { links }),
84
+ ...(isTruthy(technology) && { technology }),
85
+ ...(isTruthy(description) && { description }),
86
+ style,
87
+ }
88
+ }
89
+
90
+ parseRelation(astNode: ast.Relation): ParsedAstRelation {
91
+ const isValid = this.isValid
92
+ const coupling = resolveRelationPoints(astNode)
93
+ const target = this.resolveFqn(coupling.target)
94
+ const source = this.resolveFqn(coupling.source)
95
+ const tags = this.parseTags(astNode) ?? this.parseTags(astNode.body)
96
+ const links = this.parseLinks(astNode.body)
97
+ const kind = astNode.kind?.ref?.name as (c4.RelationshipKind | undefined)
98
+ const metadata = this.getMetadata(astNode.body?.props.find(ast.isMetadataProperty))
99
+ const astPath = this.getAstNodePath(astNode)
100
+
101
+ const bodyProps = mapToObj(
102
+ astNode.body?.props.filter(ast.isRelationStringProperty).filter(p => isNonNullish(p.value)) ?? [],
103
+ p => [p.key, p.value],
104
+ )
105
+
106
+ const navigateTo = pipe(
107
+ astNode.body?.props ?? [],
108
+ filter(ast.isRelationNavigateToProperty),
109
+ map(p => p.value.view.ref?.name),
110
+ filter(isTruthy),
111
+ first(),
112
+ )
113
+
114
+ const title = removeIndent(astNode.title ?? bodyProps.title) ?? ''
115
+ const description = removeIndent(bodyProps.description)
116
+ const technology = toSingleLine(astNode.technology) ?? removeIndent(bodyProps.technology)
117
+
118
+ const styleProp = astNode.body?.props.find(ast.isRelationStyleProperty)
119
+ const id = stringHash(
120
+ astPath,
121
+ source,
122
+ target,
123
+ ) as c4.RelationId
124
+ return {
125
+ id,
126
+ astPath,
127
+ source,
128
+ target,
129
+ title,
130
+ ...(metadata && { metadata }),
131
+ ...(isTruthy(technology) && { technology }),
132
+ ...(isTruthy(description) && { description }),
133
+ ...(kind && { kind }),
134
+ ...(tags && { tags }),
135
+ ...(isNonEmptyArray(links) && { links }),
136
+ ...toRelationshipStyleExcludeDefaults(styleProp?.props, isValid),
137
+ ...(navigateTo && { navigateTo: navigateTo as c4.ViewId }),
138
+ }
139
+ }
140
+ }
141
+ }
@@ -0,0 +1,291 @@
1
+ import type * as c4 from '@likec4/core'
2
+ import { invariant, nonexhaustive } from '@likec4/core'
3
+ import { isDefined, isTruthy } from 'remeda'
4
+ import { ast, parseAstOpacityProperty, toColor } from '../../ast'
5
+ import { logWarnError } from '../../logger'
6
+ import { elementRef } from '../../utils/elementRef'
7
+ import { parseWhereClause } from '../model-parser-where'
8
+ import { type Base, removeIndent } from './Base'
9
+
10
+ export type WithPredicates = ReturnType<typeof PredicatesParser>
11
+
12
+ export function PredicatesParser<TBase extends Base>(B: TBase) {
13
+ return class PredicatesParser extends B {
14
+ parsePredicate(astNode: ast.Predicate): c4.Expression {
15
+ if (ast.isElementPredicate(astNode)) {
16
+ return this.parseElementPredicate(astNode)
17
+ }
18
+ if (ast.isRelationPredicate(astNode)) {
19
+ return this.parseRelationPredicate(astNode)
20
+ }
21
+ nonexhaustive(astNode)
22
+ }
23
+
24
+ parseElementPredicate(astNode: ast.ElementPredicate): c4.ElementPredicateExpression {
25
+ if (ast.isElementPredicateWith(astNode)) {
26
+ return this.parseElementPredicateWith(astNode)
27
+ }
28
+ if (ast.isElementPredicateWhere(astNode)) {
29
+ return this.parseElementPredicateWhere(astNode)
30
+ }
31
+ if (ast.isElementExpression(astNode)) {
32
+ return this.parseElementExpression(astNode)
33
+ }
34
+ nonexhaustive(astNode)
35
+ }
36
+
37
+ parseElementExpressionsIterator(astNode: ast.ElementExpressionsIterator): c4.ElementExpression[] {
38
+ const exprs = [] as c4.ElementExpression[]
39
+ let iter: ast.ElementExpressionsIterator['prev'] = astNode
40
+ while (iter) {
41
+ try {
42
+ if (iter.value) {
43
+ exprs.unshift(this.parseElementExpression(iter.value))
44
+ }
45
+ } catch (e) {
46
+ logWarnError(e)
47
+ }
48
+ iter = iter.prev
49
+ }
50
+ return exprs
51
+ }
52
+
53
+ parseElementExpression(astNode: ast.ElementExpression): c4.ElementExpression {
54
+ if (ast.isWildcardExpression(astNode)) {
55
+ return {
56
+ wildcard: true
57
+ }
58
+ }
59
+ if (ast.isElementKindExpression(astNode)) {
60
+ invariant(astNode.kind?.ref, 'ElementKindExpr kind is not resolved: ' + astNode.$cstNode?.text)
61
+ return {
62
+ elementKind: astNode.kind.ref.name as c4.ElementKind,
63
+ isEqual: astNode.isEqual
64
+ }
65
+ }
66
+ if (ast.isElementTagExpression(astNode)) {
67
+ invariant(astNode.tag?.ref, 'ElementTagExpr tag is not resolved: ' + astNode.$cstNode?.text)
68
+ let elementTag = astNode.tag.$refText
69
+ if (elementTag.startsWith('#')) {
70
+ elementTag = elementTag.slice(1)
71
+ }
72
+ return {
73
+ elementTag: elementTag as c4.Tag,
74
+ isEqual: astNode.isEqual
75
+ }
76
+ }
77
+ if (ast.isExpandElementExpression(astNode)) {
78
+ const elementNode = elementRef(astNode.expand)
79
+ invariant(elementNode, 'Element not found ' + astNode.expand.$cstNode?.text)
80
+ const expanded = this.resolveFqn(elementNode)
81
+ return {
82
+ expanded
83
+ }
84
+ }
85
+ if (ast.isElementDescedantsExpression(astNode)) {
86
+ const elementNode = elementRef(astNode.parent)
87
+ invariant(elementNode, 'Element not found ' + astNode.parent.$cstNode?.text)
88
+ const element = this.resolveFqn(elementNode)
89
+ return {
90
+ element,
91
+ isChildren: astNode.suffix === '.*',
92
+ isDescendants: astNode.suffix === '.**'
93
+ }
94
+ }
95
+ if (ast.isElementRef(astNode)) {
96
+ const elementNode = elementRef(astNode)
97
+ invariant(elementNode, 'Element not found ' + astNode.$cstNode?.text)
98
+ const element = this.resolveFqn(elementNode)
99
+ return {
100
+ element
101
+ }
102
+ }
103
+ nonexhaustive(astNode)
104
+ }
105
+
106
+ parseElementPredicateWhere(astNode: ast.ElementPredicateWhere): c4.ElementWhereExpr {
107
+ const expr = this.parseElementExpression(astNode.subject)
108
+ return {
109
+ where: {
110
+ expr,
111
+ condition: astNode.where ? parseWhereClause(astNode.where) : {
112
+ kind: { neq: '--always-true--' }
113
+ }
114
+ }
115
+ }
116
+ }
117
+
118
+ parseElementPredicateWith(astNode: ast.ElementPredicateWith): c4.CustomElementExpr {
119
+ const expr = this.parseElementPredicate(astNode.subject)
120
+ const props = astNode.custom?.props ?? []
121
+ return props.reduce(
122
+ (acc, prop) => {
123
+ if (!this.isValid(prop)) {
124
+ return acc
125
+ }
126
+ if (ast.isNavigateToProperty(prop)) {
127
+ const viewId = prop.value.view.$refText
128
+ if (isTruthy(viewId)) {
129
+ acc.custom.navigateTo = viewId as c4.ViewId
130
+ }
131
+ return acc
132
+ }
133
+ if (ast.isElementStringProperty(prop)) {
134
+ if (isDefined(prop.value)) {
135
+ acc.custom[prop.key] = removeIndent(prop.value) || ''
136
+ }
137
+ return acc
138
+ }
139
+ if (ast.isIconProperty(prop)) {
140
+ const value = prop.libicon?.ref?.name ?? prop.value
141
+ if (isDefined(value)) {
142
+ acc.custom[prop.key] = value as c4.IconUrl
143
+ }
144
+ return acc
145
+ }
146
+ if (ast.isColorProperty(prop)) {
147
+ const value = toColor(prop)
148
+ if (isDefined(value)) {
149
+ acc.custom[prop.key] = value
150
+ }
151
+ return acc
152
+ }
153
+ if (ast.isShapeProperty(prop)) {
154
+ if (isDefined(prop.value)) {
155
+ acc.custom[prop.key] = prop.value
156
+ }
157
+ return acc
158
+ }
159
+ if (ast.isBorderProperty(prop)) {
160
+ if (isDefined(prop.value)) {
161
+ acc.custom[prop.key] = prop.value
162
+ }
163
+ return acc
164
+ }
165
+ if (ast.isOpacityProperty(prop)) {
166
+ if (isDefined(prop.value)) {
167
+ acc.custom[prop.key] = parseAstOpacityProperty(prop)
168
+ }
169
+ return acc
170
+ }
171
+ if (ast.isNotationProperty(prop)) {
172
+ if (isTruthy(prop.value)) {
173
+ acc.custom[prop.key] = removeIndent(prop.value)
174
+ }
175
+ return acc
176
+ }
177
+ nonexhaustive(prop)
178
+ },
179
+ {
180
+ custom: {
181
+ expr
182
+ }
183
+ } as c4.CustomElementExpr
184
+ )
185
+ }
186
+
187
+ parseRelationPredicate(astNode: ast.RelationPredicate): c4.RelationPredicateExpression {
188
+ if (ast.isRelationPredicateWith(astNode)) {
189
+ let relation = ast.isRelationPredicateWhere(astNode.subject)
190
+ ? this.parseRelationPredicateWhere(astNode.subject)
191
+ : this.parseRelationExpression(astNode.subject)
192
+
193
+ return this.parseRelationPredicateWith(astNode, relation)
194
+ }
195
+ if (ast.isRelationPredicateWhere(astNode)) {
196
+ return this.parseRelationPredicateWhere(astNode)
197
+ }
198
+ if (ast.isRelationExpression(astNode)) {
199
+ return this.parseRelationExpression(astNode)
200
+ }
201
+ nonexhaustive(astNode)
202
+ }
203
+
204
+ parseRelationPredicateWhere(astNode: ast.RelationPredicateWhere): c4.RelationWhereExpr {
205
+ const expr = this.parseRelationExpression(astNode.subject)
206
+ return {
207
+ where: {
208
+ expr,
209
+ condition: astNode.where ? parseWhereClause(astNode.where) : {
210
+ kind: { neq: '--always-true--' }
211
+ }
212
+ }
213
+ }
214
+ }
215
+
216
+ parseRelationPredicateWith(
217
+ astNode: ast.RelationPredicateWith,
218
+ relation: c4.RelationExpression | c4.RelationWhereExpr
219
+ ): c4.CustomRelationExpr {
220
+ const props = astNode.custom?.props ?? []
221
+ return props.reduce(
222
+ (acc, prop) => {
223
+ if (ast.isRelationStringProperty(prop) || ast.isNotationProperty(prop) || ast.isNotesProperty(prop)) {
224
+ if (isDefined(prop.value)) {
225
+ acc.customRelation[prop.key] = removeIndent(prop.value) ?? ''
226
+ }
227
+ return acc
228
+ }
229
+ if (ast.isArrowProperty(prop)) {
230
+ if (isTruthy(prop.value)) {
231
+ acc.customRelation[prop.key] = prop.value
232
+ }
233
+ return acc
234
+ }
235
+ if (ast.isColorProperty(prop)) {
236
+ const value = toColor(prop)
237
+ if (isTruthy(value)) {
238
+ acc.customRelation[prop.key] = value
239
+ }
240
+ return acc
241
+ }
242
+ if (ast.isLineProperty(prop)) {
243
+ if (isTruthy(prop.value)) {
244
+ acc.customRelation[prop.key] = prop.value
245
+ }
246
+ return acc
247
+ }
248
+ if (ast.isRelationNavigateToProperty(prop)) {
249
+ const viewId = prop.value.view.ref?.name
250
+ if (isTruthy(viewId)) {
251
+ acc.customRelation.navigateTo = viewId as c4.ViewId
252
+ }
253
+ return acc
254
+ }
255
+ nonexhaustive(prop)
256
+ },
257
+ {
258
+ customRelation: {
259
+ relation
260
+ }
261
+ } as c4.CustomRelationExpr
262
+ )
263
+ }
264
+
265
+ parseRelationExpression(astNode: ast.RelationExpression): c4.RelationExpression {
266
+ if (ast.isDirectedRelationExpression(astNode)) {
267
+ return {
268
+ source: this.parseElementExpression(astNode.source.from),
269
+ target: this.parseElementExpression(astNode.target),
270
+ isBidirectional: astNode.source.isBidirectional
271
+ }
272
+ }
273
+ if (ast.isInOutRelationExpression(astNode)) {
274
+ return {
275
+ inout: this.parseElementExpression(astNode.inout.to)
276
+ }
277
+ }
278
+ if (ast.isOutgoingRelationExpression(astNode)) {
279
+ return {
280
+ outgoing: this.parseElementExpression(astNode.from)
281
+ }
282
+ }
283
+ if (ast.isIncomingRelationExpression(astNode)) {
284
+ return {
285
+ incoming: this.parseElementExpression(astNode.to)
286
+ }
287
+ }
288
+ nonexhaustive(astNode)
289
+ }
290
+ }
291
+ }
@@ -0,0 +1,133 @@
1
+ import type * as c4 from '@likec4/core'
2
+ import type { HexColorLiteral } from '@likec4/core'
3
+ import { filter, isNonNullish, isTruthy, mapToObj, pipe } from 'remeda'
4
+ import { ast, toElementStyle, toRelationshipStyleExcludeDefaults } from '../../ast'
5
+ import { logger, logWarnError } from '../../logger'
6
+ import { type Base, removeIndent } from './Base'
7
+
8
+ export function SpecificationParser<TBase extends Base>(B: TBase) {
9
+ return class SpecificationParser extends B {
10
+ parseSpecification() {
11
+ const {
12
+ parseResult: {
13
+ value: {
14
+ specifications
15
+ }
16
+ },
17
+ c4Specification
18
+ } = this.doc
19
+ const isValid = this.isValid
20
+
21
+ const element_specs = specifications.flatMap(s => s.elements.filter(this.isValid))
22
+ for (const { kind, props } of element_specs) {
23
+ try {
24
+ const kindName = kind.name as c4.ElementKind
25
+ if (!isTruthy(kindName)) {
26
+ continue
27
+ }
28
+ if (kindName in c4Specification.elements) {
29
+ logger.warn(`Element kind "${kindName}" is already defined`)
30
+ continue
31
+ }
32
+ const style = props.find(ast.isElementStyleProperty)
33
+ const bodyProps = pipe(
34
+ props.filter(ast.isSpecificationElementStringProperty) ?? [],
35
+ filter(p => this.isValid(p) && isNonNullish(p.value)),
36
+ mapToObj(p => [p.key, removeIndent(p.value)] satisfies [string, string])
37
+ )
38
+ c4Specification.elements[kindName] = {
39
+ ...bodyProps,
40
+ style: {
41
+ ...toElementStyle(style?.props, this.isValid)
42
+ }
43
+ }
44
+ } catch (e) {
45
+ logWarnError(e)
46
+ }
47
+ }
48
+
49
+ const relations_specs = specifications.flatMap(s => s.relationships.filter(this.isValid))
50
+ for (const { kind, props } of relations_specs) {
51
+ try {
52
+ const kindName = kind.name as c4.RelationshipKind
53
+ if (!isTruthy(kindName)) {
54
+ continue
55
+ }
56
+ if (kindName in c4Specification.relationships) {
57
+ logger.warn(`Relationship kind "${kindName}" is already defined`)
58
+ continue
59
+ }
60
+ const bodyProps = pipe(
61
+ props.filter(ast.isSpecificationRelationshipStringProperty) ?? [],
62
+ filter(p => this.isValid(p) && isNonNullish(p.value)),
63
+ mapToObj(p => [p.key, removeIndent(p.value)] satisfies [string, string])
64
+ )
65
+ c4Specification.relationships[kindName] = {
66
+ ...bodyProps,
67
+ ...toRelationshipStyleExcludeDefaults(props, this.isValid)
68
+ }
69
+ } catch (e) {
70
+ logWarnError(e)
71
+ }
72
+ }
73
+
74
+ const tags_specs = specifications.flatMap(s => s.tags.filter(this.isValid))
75
+ for (const tagSpec of tags_specs) {
76
+ const tag = tagSpec.tag.name as c4.Tag
77
+ if (isTruthy(tag)) {
78
+ c4Specification.tags.add(tag)
79
+ }
80
+ }
81
+
82
+ const deploymentNodes_specs = specifications.flatMap(s => s.deploymentNodes.filter(isValid))
83
+ for (const deploymentNode of deploymentNodes_specs) {
84
+ try {
85
+ Object.assign(c4Specification.deployments, this.parseSpecificationDeploymentNodeKind(deploymentNode))
86
+ } catch (e) {
87
+ logWarnError(e)
88
+ }
89
+ }
90
+
91
+ const colors_specs = specifications.flatMap(s => s.colors.filter(isValid))
92
+ for (const { name, color } of colors_specs) {
93
+ try {
94
+ const colorName = name.name as c4.CustomColor
95
+ if (colorName in c4Specification.colors) {
96
+ logger.warn(`Custom color "${colorName}" is already defined`)
97
+ continue
98
+ }
99
+
100
+ c4Specification.colors[colorName] = {
101
+ color: color as HexColorLiteral
102
+ }
103
+ } catch (e) {
104
+ logWarnError(e)
105
+ }
106
+ }
107
+ }
108
+
109
+ parseSpecificationDeploymentNodeKind(
110
+ { kind, props }: ast.SpecificationDeploymentNodeKind
111
+ ): { [key: c4.DeploymentNodeKind]: c4.DeploymentNodeKindSpecification } {
112
+ const kindName = kind.name as c4.DeploymentNodeKind
113
+ if (!isTruthy(kindName)) {
114
+ throw new Error('DeploymentNodeKind name is not resolved')
115
+ }
116
+
117
+ const style = props.find(ast.isElementStyleProperty)
118
+ const bodyProps = pipe(
119
+ props.filter(ast.isSpecificationElementStringProperty) ?? [],
120
+ filter(p => this.isValid(p) && isNonNullish(p.value)),
121
+ mapToObj(p => [p.key, removeIndent(p.value)] satisfies [string, string])
122
+ )
123
+ return {
124
+ [kindName]: {
125
+ ...bodyProps,
126
+ style: {
127
+ ...toElementStyle(style?.props, this.isValid)
128
+ }
129
+ }
130
+ }
131
+ }
132
+ }
133
+ }