@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.
Files changed (89) hide show
  1. package/contrib/likec4.tmLanguage.json +1 -1
  2. package/dist/browser.cjs +1 -1
  3. package/dist/browser.d.cts +2 -2
  4. package/dist/browser.d.mts +2 -2
  5. package/dist/browser.d.ts +2 -2
  6. package/dist/browser.mjs +2 -2
  7. package/dist/index.cjs +1 -1
  8. package/dist/index.d.cts +2 -2
  9. package/dist/index.d.mts +2 -2
  10. package/dist/index.d.ts +2 -2
  11. package/dist/index.mjs +2 -2
  12. package/dist/protocol.d.cts +8 -5
  13. package/dist/protocol.d.mts +8 -5
  14. package/dist/protocol.d.ts +8 -5
  15. package/dist/shared/{language-server.DKV_FdPN.cjs → language-server.CO_nmHiL.cjs} +5598 -4213
  16. package/dist/shared/{language-server.BQRvVmE0.d.cts → language-server.Da6ey08o.d.cts} +390 -74
  17. package/dist/shared/{language-server.BysPcTxr.d.ts → language-server.De7S3e5Z.d.ts} +390 -74
  18. package/dist/shared/{language-server._wkyPgso.d.mts → language-server.Dj4iDjtB.d.mts} +390 -74
  19. package/dist/shared/{language-server.BIbAD1T-.mjs → language-server.oO_9JoAG.mjs} +5604 -4230
  20. package/package.json +11 -25
  21. package/src/Rpc.ts +6 -3
  22. package/src/ast.ts +124 -71
  23. package/src/generated/ast.ts +655 -39
  24. package/src/generated/grammar.ts +1 -1
  25. package/src/index.ts +1 -0
  26. package/src/like-c4.langium +170 -22
  27. package/src/logger.ts +7 -2
  28. package/src/lsp/CodeLensProvider.ts +0 -1
  29. package/src/lsp/CompletionProvider.ts +17 -2
  30. package/src/lsp/DocumentSymbolProvider.ts +5 -2
  31. package/src/lsp/HoverProvider.ts +34 -2
  32. package/src/lsp/SemanticTokenProvider.ts +58 -32
  33. package/src/model/deployments-index.ts +218 -0
  34. package/src/model/fqn-computation.ts +1 -1
  35. package/src/model/fqn-index.ts +0 -1
  36. package/src/model/index.ts +1 -0
  37. package/src/model/model-builder.ts +172 -37
  38. package/src/model/model-locator.ts +36 -7
  39. package/src/model/model-parser.ts +554 -92
  40. package/src/model-change/changeViewLayout.ts +2 -2
  41. package/src/module.ts +10 -4
  42. package/src/protocol.ts +10 -6
  43. package/src/references/index.ts +1 -0
  44. package/src/references/name-provider.ts +37 -0
  45. package/src/references/scope-computation.ts +130 -21
  46. package/src/references/scope-provider.ts +63 -36
  47. package/src/shared/NodeKindProvider.ts +15 -3
  48. package/src/utils/deploymentRef.ts +31 -0
  49. package/src/{elementRef.ts → utils/elementRef.ts} +1 -1
  50. package/src/utils/stringHash.ts +2 -2
  51. package/src/validation/_shared.ts +7 -5
  52. package/src/validation/deployment-checks.ts +144 -0
  53. package/src/validation/dynamic-view-step.ts +1 -1
  54. package/src/validation/index.ts +7 -0
  55. package/src/validation/relation.ts +1 -1
  56. package/src/validation/view-predicates/deployments.ts +56 -0
  57. package/src/validation/view-predicates/index.ts +1 -0
  58. package/src/view-utils/assignNavigateTo.ts +6 -5
  59. package/src/view-utils/index.ts +0 -1
  60. package/dist/model-graph/index.cjs +0 -10
  61. package/dist/model-graph/index.d.cts +0 -81
  62. package/dist/model-graph/index.d.mts +0 -81
  63. package/dist/model-graph/index.d.ts +0 -81
  64. package/dist/model-graph/index.mjs +0 -1
  65. package/dist/shared/language-server.D2QdbOJO.cjs +0 -1995
  66. package/dist/shared/language-server.DGrBGmsd.mjs +0 -1981
  67. package/src/model-graph/LikeC4ModelGraph.ts +0 -338
  68. package/src/model-graph/compute-view/__test__/fixture.ts +0 -630
  69. package/src/model-graph/compute-view/compute.ts +0 -788
  70. package/src/model-graph/compute-view/index.ts +0 -33
  71. package/src/model-graph/compute-view/predicates.ts +0 -509
  72. package/src/model-graph/dynamic-view/__test__/fixture.ts +0 -61
  73. package/src/model-graph/dynamic-view/compute.ts +0 -313
  74. package/src/model-graph/dynamic-view/index.ts +0 -29
  75. package/src/model-graph/index.ts +0 -3
  76. package/src/model-graph/utils/applyCustomElementProperties.ts +0 -65
  77. package/src/model-graph/utils/applyCustomRelationProperties.ts +0 -41
  78. package/src/model-graph/utils/applyViewRuleStyles.ts +0 -49
  79. package/src/model-graph/utils/buildComputeNodes.ts +0 -113
  80. package/src/model-graph/utils/buildElementNotations.ts +0 -63
  81. package/src/model-graph/utils/elementExpressionToPredicate.ts +0 -39
  82. package/src/model-graph/utils/relationExpressionToPredicates.ts +0 -43
  83. package/src/model-graph/utils/sortNodes.ts +0 -105
  84. package/src/model-graph/utils/uniqueTags.test.ts +0 -42
  85. package/src/model-graph/utils/uniqueTags.ts +0 -19
  86. package/src/utils/graphlib.ts +0 -9
  87. package/src/view-utils/resolve-extended-views.ts +0 -66
  88. package/src/view-utils/resolve-global-rules.ts +0 -88
  89. package/src/view-utils/view-hash.ts +0 -27
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@likec4/language-server",
3
3
  "description": "LikeC4 Language Server",
4
- "version": "1.17.1",
4
+ "version": "1.18.0",
5
5
  "license": "MIT",
6
6
  "bugs": "https://github.com/likec4/likec4/issues",
7
7
  "homepage": "https://likec4.dev",
@@ -49,11 +49,6 @@
49
49
  "types": "./dist/protocol.d.ts",
50
50
  "import": "./dist/protocol.mjs",
51
51
  "require": "./dist/protocol.cjs"
52
- },
53
- "./model-graph": {
54
- "types": "./dist/model-graph/index.d.ts",
55
- "import": "./dist/model-graph/index.mjs",
56
- "require": "./dist/model-graph/index.cjs"
57
52
  }
58
53
  },
59
54
  "publishConfig": {
@@ -86,11 +81,6 @@
86
81
  "types": "./dist/protocol.d.ts",
87
82
  "import": "./dist/protocol.mjs",
88
83
  "require": "./dist/protocol.cjs"
89
- },
90
- "./model-graph": {
91
- "types": "./dist/model-graph/index.d.ts",
92
- "import": "./dist/model-graph/index.mjs",
93
- "require": "./dist/model-graph/index.cjs"
94
84
  }
95
85
  }
96
86
  },
@@ -111,21 +101,19 @@
111
101
  "test:watch": "vitest"
112
102
  },
113
103
  "dependencies": {
114
- "@dagrejs/dagre": "^1.1.4",
115
- "@likec4/core": "1.17.1",
116
- "@likec4/log": "1.17.1",
104
+ "@likec4/core": "1.18.0",
105
+ "@likec4/log": "1.18.0",
117
106
  "@msgpack/msgpack": "^3.0.0-beta2",
118
107
  "@smithy/util-base64": "^3.0.0",
108
+ "esm-env": "^1.2.1",
119
109
  "fast-equals": "^5.0.1",
120
110
  "indent-string": "^5.0.0",
121
111
  "json5": "^2.2.3",
122
112
  "langium": "3.3.0",
123
- "object-hash": "^3.0.0",
124
113
  "p-debounce": "^4.0.0",
125
- "remeda": "^2.17.3",
126
- "string-hash": "^1.1.3",
114
+ "remeda": "^2.17.4",
127
115
  "strip-indent": "^4.0.0",
128
- "type-fest": "4.27.1",
116
+ "type-fest": "4.28.1",
129
117
  "ufo": "^1.5.4",
130
118
  "vscode-jsonrpc": "8.2.0",
131
119
  "vscode-languageserver": "9.0.1",
@@ -133,20 +121,18 @@
133
121
  "vscode-uri": "3.0.8"
134
122
  },
135
123
  "devDependencies": {
136
- "@likec4/icons": "1.17.1",
137
- "@likec4/tsconfig": "1.17.1",
124
+ "@likec4/icons": "1.18.0",
125
+ "@likec4/tsconfig": "1.18.0",
138
126
  "@types/node": "^20.17.7",
139
- "@types/object-hash": "^3.0.6",
140
- "@types/string-hash": "^1.1.3",
141
- "@vitest/coverage-v8": "^2.1.5",
127
+ "@vitest/coverage-v8": "^2.1.8",
142
128
  "execa": "^9.3.1",
143
129
  "langium-cli": "3.3.0",
144
130
  "npm-run-all2": "^7.0.1",
145
131
  "tsx": "~4.9.3",
146
- "turbo": "^2.3.1",
132
+ "turbo": "^2.3.3",
147
133
  "typescript": "^5.7.2",
148
134
  "unbuild": "^3.0.0-rc.11",
149
- "vitest": "^2.1.5"
135
+ "vitest": "^2.1.8"
150
136
  },
151
137
  "packageManager": "yarn@4.5.3"
152
138
  }
package/src/Rpc.ts CHANGED
@@ -42,9 +42,9 @@ export class Rpc implements Disposable {
42
42
  })
43
43
  },
44
44
  {
45
- timing: 'both',
46
- waitMs: 250,
47
- maxWaitMs: 500
45
+ timing: 'trailing',
46
+ waitMs: 300,
47
+ maxWaitMs: 1000
48
48
  }
49
49
  )
50
50
 
@@ -114,6 +114,9 @@ export class Rpc implements Disposable {
114
114
  if ('view' in params) {
115
115
  return modelLocator.locateView(params.view)
116
116
  }
117
+ if ('deployment' in params) {
118
+ return modelLocator.locateDeploymentElement(params.deployment, params.property ?? 'name')
119
+ }
117
120
  nonexhaustive(params)
118
121
  }),
119
122
  connection.onRequest(changeView, async (request, _cancelToken) => {
package/src/ast.ts CHANGED
@@ -1,15 +1,15 @@
1
- import { DefaultArrowType, DefaultLineStyle, DefaultRelationshipColor, nonexhaustive } from '@likec4/core'
2
1
  import type * as c4 from '@likec4/core'
2
+ import { DefaultArrowType, DefaultLineStyle, DefaultRelationshipColor, nonexhaustive } from '@likec4/core'
3
3
  import type { AstNode, AstNodeDescription, DiagnosticInfo, LangiumDocument, MultiMap } from 'langium'
4
4
  import { DocumentState } from 'langium'
5
5
  import { clamp, isDefined, isNullish, isTruthy } from 'remeda'
6
6
  import type { ConditionalPick, SetRequired, ValueOf } from 'type-fest'
7
7
  import type { Diagnostic } from 'vscode-languageserver-types'
8
8
  import { DiagnosticSeverity } from 'vscode-languageserver-types'
9
- import { elementRef } from './elementRef'
10
9
  import type { LikeC4Grammar } from './generated/ast'
11
10
  import * as ast from './generated/ast'
12
11
  import { LikeC4LanguageMetaData } from './generated/module'
12
+ import { elementRef } from './utils/elementRef'
13
13
 
14
14
  export { ast }
15
15
 
@@ -20,10 +20,19 @@ declare module './generated/ast' {
20
20
  [idattr]?: c4.Fqn | undefined
21
21
  }
22
22
  export interface ElementView {
23
- [idattr]?: c4.ViewID | undefined
23
+ [idattr]?: c4.ViewId | undefined
24
24
  }
25
25
  export interface DynamicView {
26
- [idattr]?: c4.ViewID | undefined
26
+ [idattr]?: c4.ViewId | undefined
27
+ }
28
+ export interface DeploymentView {
29
+ [idattr]?: c4.ViewId | undefined
30
+ }
31
+ export interface DeploymentNode {
32
+ [idattr]?: c4.Fqn | undefined
33
+ }
34
+ export interface DeployedInstance {
35
+ [idattr]?: c4.Fqn | undefined
27
36
  }
28
37
  }
29
38
 
@@ -59,6 +68,7 @@ export interface ParsedAstSpecification {
59
68
  color: c4.HexColorLiteral
60
69
  }
61
70
  >
71
+ deployments: Record<c4.DeploymentNodeKind, c4.DeploymentNodeKindSpecification>
62
72
  }
63
73
 
64
74
  export interface ParsedAstElement {
@@ -75,7 +85,7 @@ export interface ParsedAstElement {
75
85
  }
76
86
 
77
87
  export interface ParsedAstRelation {
78
- id: c4.RelationID
88
+ id: c4.RelationId
79
89
  astPath: string
80
90
  source: c4.Fqn
81
91
  target: c4.Fqn
@@ -89,10 +99,20 @@ export interface ParsedAstRelation {
89
99
  head?: c4.RelationshipArrowType
90
100
  tail?: c4.RelationshipArrowType
91
101
  links?: c4.NonEmptyArray<ParsedLink>
92
- navigateTo?: c4.ViewID
102
+ navigateTo?: c4.ViewId
93
103
  metadata?: { [key: string]: string }
94
104
  }
95
105
 
106
+ // Alias for easier refactoring
107
+ export type ParsedAstDeployment = c4.DeploymentElement
108
+ export namespace ParsedAstDeployment {
109
+ export type Node = c4.DeploymentNode
110
+ export type Instance = c4.DeployedInstance
111
+ }
112
+ export type ParsedAstDeploymentRelation = c4.DeploymentRelation & {
113
+ astPath: string
114
+ }
115
+
96
116
  // export interface ParsedAstGlobals {
97
117
  // predicates: Record<c4.GlobalElRelID, c4.NonEmptyArray<c4.ViewRulePredicate>>
98
118
  // dynamicPredicates: Record<c4.GlobalElRelID, c4.NonEmptyArray<c4.DynamicViewIncludeRule>>
@@ -103,9 +123,9 @@ export type ParsedAstGlobals = c4.ModelGlobals
103
123
 
104
124
  export interface ParsedAstElementView {
105
125
  __: 'element'
106
- id: c4.ViewID
126
+ id: c4.ViewId
107
127
  viewOf?: c4.Fqn
108
- extends?: c4.ViewID
128
+ extends?: c4.ViewId
109
129
  astPath: string
110
130
  title: string | null
111
131
  description: string | null
@@ -117,7 +137,7 @@ export interface ParsedAstElementView {
117
137
 
118
138
  export interface ParsedAstDynamicView {
119
139
  __: 'dynamic'
120
- id: c4.ViewID
140
+ id: c4.ViewId
121
141
  astPath: string
122
142
  title: string | null
123
143
  description: string | null
@@ -128,13 +148,24 @@ export interface ParsedAstDynamicView {
128
148
  manualLayout?: c4.ViewManualLayout
129
149
  }
130
150
 
131
- export type ParsedAstView = ParsedAstElementView | ParsedAstDynamicView
151
+ export interface ParsedAstDeploymentView {
152
+ __: 'deployment'
153
+ id: c4.ViewId
154
+ astPath: string
155
+ title: string | null
156
+ description: string | null
157
+ tags: c4.NonEmptyArray<c4.Tag> | null
158
+ links: c4.NonEmptyArray<ParsedLink> | null
159
+ rules: Array<c4.DeploymentViewRule>
160
+ }
161
+
162
+ export type ParsedAstView = ParsedAstElementView | ParsedAstDynamicView | ParsedAstDeploymentView
132
163
  export const ViewOps = {
133
- writeId<T extends ast.LikeC4View>(node: T, id: c4.ViewID): T {
164
+ writeId<T extends ast.LikeC4View>(node: T, id: c4.ViewId): T {
134
165
  node[idattr] = id
135
166
  return node
136
167
  },
137
- readId(node: ast.LikeC4View): c4.ViewID | undefined {
168
+ readId(node: ast.LikeC4View): c4.ViewId | undefined {
138
169
  return node[idattr]
139
170
  }
140
171
  }
@@ -145,7 +176,7 @@ export interface ParsedLink {
145
176
  }
146
177
 
147
178
  export const ElementOps = {
148
- writeId(node: ast.Element, id: c4.Fqn | null) {
179
+ writeId(node: ast.Element | ast.DeploymentElement, id: c4.Fqn | null) {
149
180
  if (isNullish(id)) {
150
181
  node[idattr] = undefined
151
182
  } else {
@@ -153,7 +184,7 @@ export const ElementOps = {
153
184
  }
154
185
  return node
155
186
  },
156
- readId(node: ast.Element) {
187
+ readId(node: ast.Element | ast.DeploymentElement) {
157
188
  return node[idattr]
158
189
  }
159
190
  }
@@ -162,6 +193,11 @@ export interface DocFqnIndexAstNodeDescription extends AstNodeDescription {
162
193
  fqn: c4.Fqn
163
194
  }
164
195
 
196
+ export interface DeploymentAstNodeDescription extends AstNodeDescription {
197
+ // Fullname of the artifact or node
198
+ fqn: string
199
+ }
200
+
165
201
  // export type LikeC4AstNode = ast.LikeC4AstType[keyof ast.LikeC4AstType]
166
202
  export type LikeC4AstNode = ValueOf<ConditionalPick<ast.LikeC4AstType, AstNode>>
167
203
  type LikeC4DocumentDiagnostic = Diagnostic & DiagnosticInfo<LikeC4AstNode>
@@ -173,21 +209,18 @@ export interface LikeC4DocumentProps {
173
209
  c4Relations?: ParsedAstRelation[]
174
210
  c4Globals?: ParsedAstGlobals
175
211
  c4Views?: ParsedAstView[]
212
+ c4Deployments?: ParsedAstDeployment[]
213
+ c4DeploymentRelations?: ParsedAstDeploymentRelation[]
176
214
  // Fqn -> Element
177
215
  c4fqnIndex?: MultiMap<c4.Fqn, DocFqnIndexAstNodeDescription>
178
216
  }
179
217
 
180
- export interface LikeC4LangiumDocument
181
- extends Omit<LangiumDocument<LikeC4Grammar>, 'diagnostics'>, LikeC4DocumentProps
182
- {}
183
- export interface FqnIndexedDocument
184
- extends Omit<LangiumDocument<LikeC4Grammar>, 'diagnostics'>, SetRequired<LikeC4DocumentProps, 'c4fqnIndex'>
185
- {}
218
+ type LikeC4GrammarDocument = Omit<LangiumDocument<LikeC4Grammar>, 'diagnostics'>
219
+
220
+ export interface LikeC4LangiumDocument extends LikeC4GrammarDocument, LikeC4DocumentProps {}
221
+ export interface FqnIndexedDocument extends SetRequired<LikeC4LangiumDocument, 'c4fqnIndex'> {}
186
222
 
187
- // export type ParsedLikeC4LangiumDocument = SetRequired<FqnIndexedDocument, keyof LikeC4DocumentProps>
188
- export interface ParsedLikeC4LangiumDocument
189
- extends Omit<LangiumDocument<LikeC4Grammar>, 'diagnostics'>, Required<LikeC4DocumentProps>
190
- {}
223
+ export interface ParsedLikeC4LangiumDocument extends LikeC4GrammarDocument, Required<LikeC4DocumentProps> {}
191
224
 
192
225
  export function cleanParsedModel(doc: LikeC4LangiumDocument) {
193
226
  const props: Required<Omit<LikeC4DocumentProps, 'c4fqnIndex' | 'diagnostics'>> = {
@@ -195,10 +228,13 @@ export function cleanParsedModel(doc: LikeC4LangiumDocument) {
195
228
  tags: new Set(),
196
229
  elements: {},
197
230
  relationships: {},
198
- colors: {}
231
+ colors: {},
232
+ deployments: {}
199
233
  },
200
234
  c4Elements: [],
201
235
  c4Relations: [],
236
+ c4Deployments: [],
237
+ c4DeploymentRelations: [],
202
238
  c4Globals: {
203
239
  predicates: {},
204
240
  dynamicPredicates: {},
@@ -228,6 +264,8 @@ export function isParsedLikeC4LangiumDocument(
228
264
  && !!doc.c4Relations
229
265
  && !!doc.c4Views
230
266
  && !!doc.c4fqnIndex
267
+ && !!doc.c4Deployments
268
+ && !!doc.c4DeploymentRelations
231
269
  )
232
270
  }
233
271
 
@@ -252,29 +290,32 @@ const isValidatableAstNode = validatableAstNodeGuards([
252
290
  ast.isRelationExpression,
253
291
  ast.isDynamicViewParallelSteps,
254
292
  ast.isDynamicViewStep,
293
+ ast.isDeploymentViewRule,
294
+ ast.isDeploymentViewRulePredicate,
295
+ ast.isDeploymentExpression,
255
296
  ast.isViewProperty,
256
297
  ast.isStyleProperty,
298
+ ast.isPredicate,
257
299
  ast.isTags,
258
300
  ast.isViewRule,
259
301
  ast.isDynamicViewRule,
260
- ast.isElementViewBody,
261
- ast.isDynamicViewBody,
262
302
  ast.isLikeC4View,
263
- ast.isRelationProperty,
264
- ast.isRelationBody,
303
+ ast.isViewRuleStyleOrGlobalRef,
304
+ ast.isDeployedInstance,
305
+ ast.isDeploymentNode,
306
+ ast.isRelationshipStyleProperty,
265
307
  ast.isRelation,
266
308
  ast.isElementProperty,
267
- ast.isElementBody,
309
+ ast.isStringProperty,
310
+ ast.isNavigateToProperty,
268
311
  ast.isElement,
269
- ast.isExtendElementBody,
270
312
  ast.isExtendElement,
271
313
  ast.isSpecificationElementKind,
272
314
  ast.isSpecificationRelationshipKind,
315
+ ast.isSpecificationDeploymentNodeKind,
273
316
  ast.isSpecificationTag,
274
317
  ast.isSpecificationColor,
275
- ast.isSpecificationRule,
276
- ast.isModelViews,
277
- ast.isModel
318
+ ast.isSpecificationRule
278
319
  ])
279
320
  type ValidatableAstNode = Guarded<typeof isValidatableAstNode>
280
321
 
@@ -291,12 +332,17 @@ const findInvalidContainer = (node: LikeC4AstNode): ValidatableAstNode | undefin
291
332
 
292
333
  export function checksFromDiagnostics(doc: LikeC4LangiumDocument) {
293
334
  const errors = doc.diagnostics?.filter(d => d.severity === DiagnosticSeverity.Error) ?? []
294
- const invalidNodes = new WeakSet(
295
- errors.flatMap(d => {
296
- return findInvalidContainer(d.node) ?? []
297
- })
298
- )
299
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
335
+ const invalidNodes = new WeakSet()
336
+ for (const { node } of errors) {
337
+ if (isNullish(node) || invalidNodes.has(node)) {
338
+ continue
339
+ }
340
+ invalidNodes.add(node)
341
+ const container = findInvalidContainer(node)
342
+ if (container) {
343
+ invalidNodes.add(container)
344
+ }
345
+ }
300
346
  const isValid = (n: ValidatableAstNode) => !invalidNodes.has(n)
301
347
  return {
302
348
  isValid,
@@ -311,11 +357,8 @@ export function* streamModel(doc: LikeC4LangiumDocument, isValid: IsValidFn) {
311
357
  const relations = [] as ast.Relation[]
312
358
  let el
313
359
  while ((el = traverseStack.shift())) {
314
- if (!isValid(el)) {
315
- continue
316
- }
317
360
  if (ast.isRelation(el)) {
318
- relations.push(el)
361
+ isValid(el) && relations.push(el)
319
362
  continue
320
363
  }
321
364
  if (ast.isExtendElement(el)) {
@@ -327,6 +370,9 @@ export function* streamModel(doc: LikeC4LangiumDocument, isValid: IsValidFn) {
327
370
  if (el.body && el.body.elements.length > 0) {
328
371
  traverseStack.push(...el.body.elements)
329
372
  }
373
+ if (!isValid(el)) {
374
+ continue
375
+ }
330
376
  yield el
331
377
  }
332
378
  for (const relation of relations) {
@@ -407,14 +453,13 @@ export function toElementStyle(props: Array<ast.StyleProperty> | undefined, isVa
407
453
  break
408
454
  }
409
455
  default:
410
- // @ts-expect-error
411
- nonexhaustive(prop.$type)
456
+ nonexhaustive(prop)
412
457
  }
413
458
  }
414
459
  return result
415
460
  }
416
461
 
417
- export function toRelationshipStyle(props?: ast.RelationshipStyleProperty[]) {
462
+ export function toRelationshipStyle(props: ast.RelationshipStyleProperty[] | undefined, isValid: IsValidFn) {
418
463
  const result = {} as {
419
464
  color?: c4.Color
420
465
  line?: c4.RelationshipLineType
@@ -425,42 +470,50 @@ export function toRelationshipStyle(props?: ast.RelationshipStyleProperty[]) {
425
470
  return result
426
471
  }
427
472
  for (const prop of props) {
428
- if (ast.isColorProperty(prop)) {
429
- const color = toColor(prop)
430
- if (isTruthy(color)) {
431
- result.color = color
432
- }
433
- continue
434
- }
435
- if (ast.isLineProperty(prop)) {
436
- result.line = prop.value
473
+ if (!isValid(prop)) {
437
474
  continue
438
475
  }
439
- if (ast.isArrowProperty(prop)) {
440
- switch (prop.key) {
441
- case 'head': {
442
- result.head = prop.value
443
- break
444
- }
445
- case 'tail': {
446
- result.tail = prop.value
447
- break
476
+ switch (true) {
477
+ case ast.isColorProperty(prop): {
478
+ const color = toColor(prop)
479
+ if (isTruthy(color)) {
480
+ result.color = color
448
481
  }
449
- default: {
450
- nonexhaustive(prop)
482
+ break
483
+ }
484
+ case ast.isLineProperty(prop): {
485
+ result.line = prop.value
486
+ break
487
+ }
488
+ case ast.isArrowProperty(prop): {
489
+ switch (prop.key) {
490
+ case 'head': {
491
+ result.head = prop.value
492
+ break
493
+ }
494
+ case 'tail': {
495
+ result.tail = prop.value
496
+ break
497
+ }
498
+ default: {
499
+ nonexhaustive(prop)
500
+ }
451
501
  }
502
+ break
503
+ }
504
+ default: {
505
+ nonexhaustive(prop)
452
506
  }
453
- continue
454
507
  }
455
- nonexhaustive(prop)
456
508
  }
457
509
  return result
458
510
  }
459
511
 
460
512
  export function toRelationshipStyleExcludeDefaults(
461
- props?: ast.SpecificationRelationshipKind['props']
513
+ props: ast.SpecificationRelationshipKind['props'] | undefined,
514
+ isValid: IsValidFn
462
515
  ) {
463
- const { color, line, head, tail } = toRelationshipStyle(props?.filter(ast.isRelationshipStyleProperty))
516
+ const { color, line, head, tail } = toRelationshipStyle(props?.filter(ast.isRelationshipStyleProperty), isValid)
464
517
  return {
465
518
  ...(color && color !== DefaultRelationshipColor ? { color } : {}),
466
519
  ...(line && line !== DefaultLineStyle ? { line } : {}),