@likec4/language-server 1.8.1 → 1.10.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 (85) hide show
  1. package/contrib/likec4.tmLanguage.json +1 -1
  2. package/dist/browser.cjs +21 -0
  3. package/dist/browser.d.cts +22 -0
  4. package/dist/browser.d.mts +22 -0
  5. package/dist/browser.d.ts +22 -0
  6. package/dist/browser.mjs +19 -0
  7. package/dist/index.cjs +10 -0
  8. package/dist/index.d.cts +18 -0
  9. package/dist/index.d.mts +18 -0
  10. package/dist/index.d.ts +18 -0
  11. package/dist/index.mjs +1 -0
  12. package/dist/likec4lib.cjs +961 -0
  13. package/dist/likec4lib.d.cts +6 -0
  14. package/dist/likec4lib.d.mts +6 -0
  15. package/dist/likec4lib.d.ts +6 -0
  16. package/dist/likec4lib.mjs +957 -0
  17. package/dist/model-graph/index.cjs +10 -0
  18. package/dist/model-graph/index.d.cts +79 -0
  19. package/dist/model-graph/index.d.mts +79 -0
  20. package/dist/model-graph/index.d.ts +79 -0
  21. package/dist/model-graph/index.mjs +1 -0
  22. package/dist/node.cjs +18 -0
  23. package/dist/node.d.cts +20 -0
  24. package/dist/node.d.mts +20 -0
  25. package/dist/node.d.ts +20 -0
  26. package/dist/node.mjs +16 -0
  27. package/dist/protocol.cjs +25 -0
  28. package/dist/protocol.d.cts +43 -0
  29. package/dist/protocol.d.mts +43 -0
  30. package/dist/protocol.d.ts +43 -0
  31. package/dist/protocol.mjs +17 -0
  32. package/dist/shared/language-server.CjFzaJwI.d.cts +1223 -0
  33. package/dist/shared/language-server.CtKHXJDD.d.ts +1223 -0
  34. package/dist/shared/language-server.D-84I33F.d.mts +1223 -0
  35. package/dist/shared/language-server.DBJJUUgF.mjs +5737 -0
  36. package/dist/shared/language-server.DtBRb9os.mjs +1656 -0
  37. package/dist/shared/language-server.DwyCJvXm.cjs +1669 -0
  38. package/dist/shared/language-server.JWkqVjGv.cjs +5748 -0
  39. package/package.json +36 -20
  40. package/src/ast.ts +48 -36
  41. package/src/browser.ts +0 -3
  42. package/src/elementRef.ts +1 -1
  43. package/src/formatting/LikeC4Formatter.ts +388 -0
  44. package/src/formatting/utils.ts +26 -0
  45. package/src/generated/ast.ts +170 -12
  46. package/src/generated/grammar.ts +1 -1
  47. package/src/generated-lib/icons.ts +1 -1
  48. package/src/like-c4.langium +49 -8
  49. package/src/likec4lib.ts +2 -3
  50. package/src/logger.ts +9 -1
  51. package/src/lsp/DocumentLinkProvider.ts +27 -15
  52. package/src/lsp/RenameProvider.ts +8 -0
  53. package/src/lsp/SemanticTokenProvider.ts +20 -2
  54. package/src/lsp/index.ts +1 -0
  55. package/src/model/fqn-computation.ts +33 -23
  56. package/src/model/fqn-index.ts +5 -21
  57. package/src/model/model-builder.ts +180 -112
  58. package/src/model/model-locator.ts +1 -1
  59. package/src/model/model-parser-where.ts +3 -2
  60. package/src/model/model-parser.ts +99 -39
  61. package/src/model-graph/LikeC4ModelGraph.ts +42 -21
  62. package/src/model-graph/compute-view/__test__/fixture.ts +16 -14
  63. package/src/model-graph/compute-view/compute.ts +110 -81
  64. package/src/model-graph/compute-view/predicates.ts +6 -8
  65. package/src/model-graph/dynamic-view/__test__/fixture.ts +1 -0
  66. package/src/model-graph/dynamic-view/compute.ts +98 -61
  67. package/src/model-graph/utils/buildElementNotations.ts +1 -1
  68. package/src/model-graph/utils/elementExpressionToPredicate.ts +1 -1
  69. package/src/model-graph/utils/sortNodes.ts +2 -6
  70. package/src/module.ts +21 -4
  71. package/src/protocol.ts +4 -5
  72. package/src/references/scope-computation.ts +10 -1
  73. package/src/references/scope-provider.ts +2 -1
  74. package/src/shared/NodeKindProvider.ts +73 -34
  75. package/src/test/setup.ts +3 -8
  76. package/src/test/testServices.ts +27 -7
  77. package/src/utils/graphlib.ts +11 -0
  78. package/src/validation/index.ts +2 -1
  79. package/src/validation/property-checks.ts +13 -1
  80. package/src/validation/specification.ts +3 -3
  81. package/src/view-utils/manual-layout.ts +1 -1
  82. package/src/view-utils/resolve-extended-views.ts +19 -10
  83. package/src/view-utils/resolve-relative-paths.ts +19 -24
  84. package/src/view-utils/view-hash.ts +1 -1
  85. package/src/reset.d.ts +0 -2
@@ -0,0 +1,388 @@
1
+ import { type AstNode, GrammarUtils } from 'langium'
2
+ import { AbstractFormatter, Formatting, type NodeFormatter } from 'langium/lsp'
3
+ import * as ast from '../generated/ast'
4
+ import * as utils from './utils'
5
+
6
+ const FormattingOptions = {
7
+ newLine: Formatting.newLine({ allowMore: true }),
8
+ oneSpace: Formatting.oneSpace(),
9
+ noSpace: Formatting.noSpace(),
10
+ indent: Formatting.indent({ allowMore: true }),
11
+ noIndent: Formatting.noIndent()
12
+ }
13
+ type Predicate<T extends AstNode> = (x: unknown) => x is T
14
+
15
+ export class LikeC4Formatter extends AbstractFormatter {
16
+ protected format(node: AstNode): void {
17
+ this.removeIndentFromTopLevelStatements(node)
18
+ this.indentContentInBraces(node)
19
+
20
+ this.formatSpecificationRule(node)
21
+ this.formatElementDeclaration(node)
22
+ this.formatRelation(node)
23
+ this.formatView(node)
24
+ this.formatViewRuleStyle(node)
25
+ this.formatIncludeExcludeExpressions(node)
26
+ this.formatWhereExpression(node)
27
+ this.formatWithPredicate(node)
28
+ this.formatLeafProperty(node)
29
+ this.formatMetadataProperty(node)
30
+ this.formatAutolayoutProperty(node)
31
+ this.formatLinkProperty(node)
32
+ this.formatNavigateToProperty(node)
33
+ this.formatTags(node)
34
+ }
35
+
36
+ protected formatTags(node: AstNode) {
37
+ this.on(node, ast.isTags, (n, f) => {
38
+ f.cst(GrammarUtils.findNodesForProperty(n.$cstNode, 'values').slice(1))
39
+ .prepend(FormattingOptions.oneSpace)
40
+
41
+ f.keywords(',')
42
+ .prepend(FormattingOptions.noSpace)
43
+ .append(FormattingOptions.oneSpace)
44
+ })
45
+ }
46
+
47
+ protected formatRelation(node: AstNode) {
48
+ this.on(node, ast.isRelation, (n, f) => {
49
+ f.property('source').append(FormattingOptions.oneSpace)
50
+ f.keywords(']->').prepend(FormattingOptions.noSpace)
51
+ f.keywords('-[').append(FormattingOptions.noSpace)
52
+
53
+ f.properties('target', 'title', 'technology', 'tags').prepend(FormattingOptions.oneSpace)
54
+ })
55
+
56
+ this.on(node, ast.isDynamicViewStep, (n, f) => {
57
+ f.properties('source').append(FormattingOptions.oneSpace)
58
+ f.keywords(']->').prepend(FormattingOptions.noSpace)
59
+ f.keywords('-[').append(FormattingOptions.noSpace)
60
+ f.properties('target', 'title').prepend(FormattingOptions.oneSpace)
61
+ })
62
+
63
+ this.on(node, ast.isDirectedRelationExpression)
64
+ ?.property('target').prepend(FormattingOptions.oneSpace)
65
+
66
+ this.on(node, ast.isOutgoingRelationExpression, (n, f) => {
67
+ f.property('from').append(FormattingOptions.oneSpace)
68
+ f.keywords(']->').prepend(FormattingOptions.noSpace)
69
+ f.keywords('-[').append(FormattingOptions.noSpace)
70
+ })
71
+
72
+ this.on(node, ast.isIncomingRelationExpression)
73
+ ?.keywords('->').append(FormattingOptions.oneSpace)
74
+
75
+ this.on(node, ast.isInOutRelationExpression)
76
+ ?.property('inout').append(FormattingOptions.oneSpace)
77
+ }
78
+
79
+ protected removeIndentFromTopLevelStatements(node: AstNode) {
80
+ if (
81
+ ast.isModel(node)
82
+ || ast.isSpecificationRule(node)
83
+ || ast.isModelViews(node)
84
+ || ast.isLikeC4Lib(node)
85
+ ) {
86
+ const formatter = this.getNodeFormatter(node)
87
+ formatter.keywords('specification', 'model', 'views', 'likec4lib')
88
+ .prepend(FormattingOptions.noIndent)
89
+ }
90
+ }
91
+
92
+ protected indentContentInBraces(node: AstNode) {
93
+ if (
94
+ ast.isLikeC4Lib(node)
95
+ || ast.isSpecificationRule(node)
96
+ || ast.isSpecificationElementKind(node)
97
+ || ast.isSpecificationRelationshipKind(node)
98
+ || ast.isModel(node)
99
+ || ast.isElementBody(node)
100
+ || ast.isExtendElementBody(node)
101
+ || ast.isRelationBody(node)
102
+ || ast.isRelationStyleProperty(node)
103
+ || ast.isMetadataBody(node)
104
+ || ast.isModelViews(node)
105
+ || ast.isElementViewBody(node)
106
+ || ast.isDynamicViewBody(node)
107
+ || ast.isViewRuleStyle(node)
108
+ || ast.isCustomElementProperties(node)
109
+ || ast.isCustomRelationProperties(node)
110
+ || ast.isElementStyleProperty(node)
111
+ ) {
112
+ const formatter = this.getNodeFormatter(node)
113
+ const openBrace = formatter.keywords('{')
114
+ const closeBrace = formatter.keywords('}')
115
+
116
+ const interiorNodes = formatter.interior(openBrace, closeBrace)
117
+
118
+ // Workaround for tags as they are parsed as overlapping regions.
119
+ // E.g. '#tag1, #tag2' will be parsed as two nodes: '#tag1' and '#tag1, #tag2'
120
+ let perviousNode = null
121
+ for (const interiorNode of interiorNodes.nodes) {
122
+ if (!perviousNode || !utils.areOverlap(perviousNode, interiorNode)) {
123
+ formatter.cst([interiorNode]).prepend(FormattingOptions.indent)
124
+ }
125
+ perviousNode = interiorNode
126
+ }
127
+
128
+ openBrace
129
+ .prepend(FormattingOptions.noIndent)
130
+ .prepend(FormattingOptions.oneSpace)
131
+ closeBrace
132
+ .prepend(FormattingOptions.noIndent)
133
+ .prepend(Formatting.newLine({ allowMore: true }))
134
+ }
135
+ }
136
+
137
+ protected appendKeywordsWithSpace(node: AstNode) {
138
+ this.on(node, ast.isElementKind)
139
+ ?.keywords('element').append(FormattingOptions.oneSpace)
140
+ }
141
+
142
+ protected formatView(node: AstNode) {
143
+ this.on(node, ast.isElementView, (n, f) => {
144
+ if (n.extends || n.viewOf || n.name) {
145
+ f.keywords('view').append(FormattingOptions.oneSpace)
146
+ }
147
+ f.keywords('of', 'extends').surround(FormattingOptions.oneSpace)
148
+ })
149
+
150
+ this.on(node, ast.isDynamicView)
151
+ ?.keywords('dynamic', 'view').append(FormattingOptions.oneSpace)
152
+ }
153
+
154
+ protected formatLeafProperty(node: AstNode) {
155
+ if (
156
+ ast.isElementStringProperty(node)
157
+ || ast.isRelationStringProperty(node)
158
+ || ast.isViewStringProperty(node)
159
+ || ast.isNotationProperty(node)
160
+ || ast.isSpecificationElementStringProperty(node)
161
+ || ast.isSpecificationRelationshipStringProperty(node)
162
+ || ast.isColorProperty(node)
163
+ || ast.isLineProperty(node)
164
+ || ast.isArrowProperty(node)
165
+ || ast.isIconProperty(node)
166
+ || ast.isShapeProperty(node)
167
+ || ast.isBorderProperty(node)
168
+ || ast.isOpacityProperty(node)
169
+ ) {
170
+ const formatter = this.getNodeFormatter(node)
171
+ formatter.keywords(
172
+ 'title',
173
+ 'description',
174
+ 'technology',
175
+ 'notation',
176
+ 'color',
177
+ 'line',
178
+ 'head',
179
+ 'tail',
180
+ 'icon',
181
+ 'shape',
182
+ 'border',
183
+ 'opacity'
184
+ )
185
+ .append(FormattingOptions.oneSpace)
186
+
187
+ formatter.keyword(':')
188
+ .prepend(FormattingOptions.noSpace)
189
+ .append(FormattingOptions.oneSpace)
190
+
191
+ formatter.keyword(';')
192
+ .prepend(FormattingOptions.noSpace)
193
+ .append(FormattingOptions.newLine)
194
+ }
195
+ }
196
+
197
+ protected formatLinkProperty(node: AstNode) {
198
+ this.on(node, ast.isLinkProperty, (n, f) => {
199
+ f.keyword('link').append(FormattingOptions.oneSpace)
200
+ f.property('value').append(FormattingOptions.oneSpace)
201
+ f.keyword(':')
202
+ .prepend(FormattingOptions.noSpace)
203
+ .append(FormattingOptions.oneSpace)
204
+
205
+ f.keyword(';')
206
+ .prepend(FormattingOptions.noSpace)
207
+ .append(FormattingOptions.newLine)
208
+ })
209
+ }
210
+
211
+ protected formatNavigateToProperty(node: AstNode) {
212
+ this.on(node, ast.isNavigateToProperty)
213
+ ?.property('key').append(FormattingOptions.oneSpace)
214
+ }
215
+
216
+ protected formatAutolayoutProperty(node: AstNode) {
217
+ this.on(node, ast.isViewRuleAutoLayout)
218
+ ?.keyword('autoLayout').append(FormattingOptions.oneSpace)
219
+ }
220
+
221
+ protected formatMetadataProperty(node: AstNode) {
222
+ this.on(node, ast.isMetadataAttribute, (n, f) => {
223
+ f.property('key').append(FormattingOptions.oneSpace)
224
+ f.keyword(':')
225
+ .prepend(FormattingOptions.noSpace)
226
+ .append(FormattingOptions.oneSpace)
227
+ f.keyword(';')
228
+ .prepend(FormattingOptions.noSpace)
229
+ .append(FormattingOptions.newLine)
230
+ })
231
+ }
232
+
233
+ protected formatElementDeclaration(node: AstNode) {
234
+ this.on(node, ast.isElement, (n, f) => {
235
+ const kind = GrammarUtils.findNodeForProperty(n.$cstNode, 'kind')
236
+ const name = GrammarUtils.findNodeForProperty(n.$cstNode, 'name')
237
+
238
+ if (name && kind) {
239
+ // system sys1
240
+ if (utils.compareRanges(name, kind) > 0) {
241
+ f.cst([kind]).append(FormattingOptions.oneSpace)
242
+ }
243
+ // sys1 = system
244
+ else {
245
+ f.cst([name]).append(FormattingOptions.oneSpace)
246
+ f.cst([kind]).prepend(FormattingOptions.oneSpace)
247
+ }
248
+ }
249
+
250
+ f.properties('props').prepend(FormattingOptions.oneSpace)
251
+ })
252
+ }
253
+
254
+ protected formatSpecificationRule(node: AstNode) {
255
+ if (
256
+ ast.isSpecificationElementKind(node)
257
+ || ast.isSpecificationRelationshipKind(node)
258
+ || ast.isSpecificationTag(node)
259
+ ) {
260
+ const formatter = this.getNodeFormatter(node)
261
+
262
+ formatter.keywords('element', 'relationship', 'tag')
263
+ .append(FormattingOptions.oneSpace)
264
+ }
265
+ if (
266
+ ast.isSpecificationColor(node)
267
+ ) {
268
+ const formatter = this.getNodeFormatter(node)
269
+ formatter.keyword('color').append(FormattingOptions.oneSpace)
270
+ formatter.property('name').append(FormattingOptions.oneSpace)
271
+ }
272
+ }
273
+
274
+ protected formatWithPredicate(node: AstNode) {
275
+ const formatter = this.getNodeFormatter(node)
276
+ if (
277
+ ast.isElementPredicateWith(node)
278
+ || ast.isRelationPredicateWith(node)
279
+ ) {
280
+ formatter.keyword('with').prepend(FormattingOptions.oneSpace)
281
+ }
282
+ }
283
+
284
+ protected formatViewRuleStyle(node: AstNode) {
285
+ this.on(node, ast.isViewRuleStyle)
286
+ ?.keyword('style').append(FormattingOptions.oneSpace)
287
+
288
+ this.on(node, ast.isElementExpressionsIterator)
289
+ ?.keyword(',')
290
+ .prepend(FormattingOptions.noSpace)
291
+ .append(FormattingOptions.oneSpace)
292
+ }
293
+
294
+ protected formatWhereExpression(node: AstNode) {
295
+ if (
296
+ ast.isRelationPredicateOrWhere(node)
297
+ || ast.isElementPredicateOrWhere(node)
298
+ ) {
299
+ const formatter = this.getNodeFormatter(node)
300
+ formatter.keyword('where').append(FormattingOptions.oneSpace)
301
+ }
302
+ if (
303
+ ast.isWhereRelationExpression(node)
304
+ || ast.isWhereElementExpression(node)
305
+ ) {
306
+ const formatter = this.getNodeFormatter(node)
307
+ formatter.property('operator').surround(FormattingOptions.oneSpace)
308
+ }
309
+ if (
310
+ ast.isWhereElementNegation(node)
311
+ || ast.isWhereRelationNegation(node)
312
+ ) {
313
+ const formatter = this.getNodeFormatter(node)
314
+ formatter.keyword('not').append(FormattingOptions.oneSpace)
315
+ }
316
+ if (
317
+ ast.isWhereElement(node)
318
+ || ast.isWhereElementTag(node)
319
+ || ast.isWhereElementKind(node)
320
+ || ast.isWhereRelation(node)
321
+ || ast.isWhereRelationTag(node)
322
+ || ast.isWhereRelationKind(node)
323
+ ) {
324
+ const formatter = this.getNodeFormatter(node)
325
+ formatter.property('operator').surround(FormattingOptions.oneSpace)
326
+ formatter.property('not').surround(FormattingOptions.oneSpace)
327
+ }
328
+ }
329
+
330
+ protected formatIncludeExcludeExpressions(node: AstNode) {
331
+ if (
332
+ ast.isDynamicViewRule(node)
333
+ || ast.isIncludePredicate(node)
334
+ || ast.isExcludePredicate(node)
335
+ ) {
336
+ const formatter = this.getNodeFormatter(node)
337
+
338
+ if (!node.$cstNode || !utils.isMultiline(node.$cstNode)) {
339
+ formatter.keywords('include', 'exclude')
340
+ .append(FormattingOptions.oneSpace)
341
+ }
342
+ }
343
+ if (
344
+ ast.isDynamicViewPredicateIterator(node)
345
+ || ast.isPredicates(node)
346
+ || ast.isPredicates(node)
347
+ ) {
348
+ const formatter = this.getNodeFormatter(node)
349
+ const parent = this.findPredicateExpressionRoot(node)
350
+ const isMultiline = parent?.$cstNode && utils.isMultiline(parent?.$cstNode)
351
+
352
+ if (isMultiline) {
353
+ formatter.property('value').prepend(FormattingOptions.indent)
354
+ }
355
+ formatter.keyword(',')
356
+ .prepend(FormattingOptions.noSpace)
357
+ .append(isMultiline ? FormattingOptions.newLine : FormattingOptions.oneSpace)
358
+ }
359
+ }
360
+
361
+ private findPredicateExpressionRoot(node: AstNode): AstNode | undefined {
362
+ let parent = node.$container
363
+ while (true) {
364
+ if (
365
+ !parent
366
+ || ast.isDynamicViewRule(parent)
367
+ || ast.isIncludePredicate(parent)
368
+ || ast.isExcludePredicate(parent)
369
+ ) {
370
+ return parent
371
+ }
372
+
373
+ parent = parent.$container
374
+ }
375
+ }
376
+
377
+ private on<T extends AstNode>(
378
+ node: AstNode,
379
+ predicate: Predicate<T>,
380
+ format?: (node: T, f: NodeFormatter<T>) => void
381
+ ): NodeFormatter<T> | undefined {
382
+ const formatter = predicate(node) ? this.getNodeFormatter(node) : undefined
383
+
384
+ format && formatter && format(node as T, formatter)
385
+
386
+ return formatter
387
+ }
388
+ }
@@ -0,0 +1,26 @@
1
+ import type { CstNode } from "langium"
2
+ import type { Position, Range } from "vscode-languageserver-types"
3
+
4
+
5
+ export function areOverlap(a: CstNode, b: CstNode): boolean {
6
+ ;[a, b] = compareRanges(a, b) > 0 ? [b, a] : [a, b]
7
+
8
+ return isInRagne(a.range, b.range.start)
9
+ }
10
+
11
+ export function compareRanges(a: CstNode, b: CstNode): number {
12
+ const lineDiff = a.range.start.line - b.range.start.line
13
+
14
+ return lineDiff !== 0 ? lineDiff : a.range.start.character - b.range.start.character
15
+ }
16
+
17
+ export function isInRagne(range: Range, pos: Position): boolean {
18
+ return !(pos.line < range.start.line
19
+ || pos.line > range.end.line
20
+ || pos.line == range.start.line && pos.character < range.start.character
21
+ || pos.line == range.end.line && pos.character > range.end.character)
22
+ }
23
+
24
+ export function isMultiline(node: CstNode): boolean {
25
+ return node.range.start.line != node.range.end.line
26
+ }