@likec4/language-server 1.1.1 → 1.2.1

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 (173) hide show
  1. package/contrib/likec4.monarch.ts +4 -4
  2. package/contrib/likec4.tmLanguage.json +1 -1
  3. package/package.json +8 -10
  4. package/src/Rpc.ts +108 -0
  5. package/src/ast.ts +443 -0
  6. package/src/browser/index.ts +30 -0
  7. package/src/elementRef.ts +26 -0
  8. package/src/generated/ast.ts +1632 -0
  9. package/src/generated/grammar.ts +10 -0
  10. package/src/generated/module.ts +32 -0
  11. package/src/index.ts +4 -0
  12. package/src/like-c4.langium +395 -0
  13. package/src/logger.ts +54 -0
  14. package/src/lsp/CodeLensProvider.ts +51 -0
  15. package/src/lsp/DocumentHighlightProvider.ts +12 -0
  16. package/src/lsp/DocumentLinkProvider.test.ts +66 -0
  17. package/src/lsp/DocumentLinkProvider.ts +53 -0
  18. package/src/lsp/DocumentSymbolProvider.ts +201 -0
  19. package/src/lsp/HoverProvider.ts +58 -0
  20. package/{dist/lsp/SemanticTokenProvider.js → src/lsp/SemanticTokenProvider.ts} +58 -43
  21. package/src/lsp/index.ts +6 -0
  22. package/src/model/fqn-computation.ts +47 -0
  23. package/src/model/fqn-index.ts +161 -0
  24. package/src/model/index.ts +5 -0
  25. package/src/model/model-builder.ts +447 -0
  26. package/src/model/model-locator.ts +130 -0
  27. package/src/model/model-parser.ts +580 -0
  28. package/src/model-change/ModelChanges.ts +120 -0
  29. package/src/model-change/changeElementStyle.ts +176 -0
  30. package/src/model-change/changeViewLayout.ts +41 -0
  31. package/src/module.ts +197 -0
  32. package/src/node/index.ts +20 -0
  33. package/src/protocol.ts +87 -0
  34. package/src/references/index.ts +2 -0
  35. package/src/references/scope-computation.ts +142 -0
  36. package/src/references/scope-provider.ts +166 -0
  37. package/src/shared/NodeKindProvider.ts +67 -0
  38. package/src/shared/WorkspaceManager.ts +39 -0
  39. package/src/shared/WorkspaceSymbolProvider.ts +3 -0
  40. package/src/shared/index.ts +3 -0
  41. package/src/test/index.ts +1 -0
  42. package/src/test/testServices.ts +119 -0
  43. package/src/utils/index.ts +1 -0
  44. package/src/utils/printDocs.ts +3 -0
  45. package/src/utils/stringHash.ts +6 -0
  46. package/src/validation/dynamic-view-rule.ts +35 -0
  47. package/src/validation/dynamic-view-step.ts +39 -0
  48. package/src/validation/element.ts +52 -0
  49. package/{dist/validation/index.js → src/validation/index.ts} +25 -17
  50. package/src/validation/property-checks.ts +17 -0
  51. package/src/validation/relation.ts +57 -0
  52. package/src/validation/specification.ts +118 -0
  53. package/src/validation/view-predicates/custom-element-expr.ts +21 -0
  54. package/src/validation/view-predicates/expanded-element.ts +34 -0
  55. package/src/validation/view-predicates/incoming.ts +19 -0
  56. package/src/validation/view-predicates/index.ts +4 -0
  57. package/src/validation/view-predicates/outgoing.ts +19 -0
  58. package/src/validation/view.ts +26 -0
  59. package/src/view-utils/assignNavigateTo.ts +30 -0
  60. package/src/view-utils/index.ts +3 -0
  61. package/src/view-utils/resolve-extended-views.ts +57 -0
  62. package/src/view-utils/resolve-relative-paths.ts +84 -0
  63. package/dist/Rpc.d.ts +0 -10
  64. package/dist/Rpc.js +0 -98
  65. package/dist/ast.d.ts +0 -133
  66. package/dist/ast.js +0 -267
  67. package/dist/browser/index.d.ts +0 -9
  68. package/dist/browser/index.js +0 -16
  69. package/dist/elementRef.d.ts +0 -12
  70. package/dist/elementRef.js +0 -15
  71. package/dist/generated/ast.d.ts +0 -559
  72. package/dist/generated/ast.js +0 -868
  73. package/dist/generated/grammar.d.ts +0 -7
  74. package/dist/generated/grammar.js +0 -3
  75. package/dist/generated/module.d.ts +0 -14
  76. package/dist/generated/module.js +0 -22
  77. package/dist/index.d.ts +0 -5
  78. package/dist/index.js +0 -2
  79. package/dist/logger.d.ts +0 -12
  80. package/dist/logger.js +0 -51
  81. package/dist/lsp/CodeLensProvider.d.ts +0 -10
  82. package/dist/lsp/CodeLensProvider.js +0 -37
  83. package/dist/lsp/DocumentHighlightProvider.d.ts +0 -10
  84. package/dist/lsp/DocumentHighlightProvider.js +0 -10
  85. package/dist/lsp/DocumentLinkProvider.d.ts +0 -11
  86. package/dist/lsp/DocumentLinkProvider.js +0 -41
  87. package/dist/lsp/DocumentLinkProvider.test.d.ts +0 -2
  88. package/dist/lsp/DocumentLinkProvider.test.js +0 -54
  89. package/dist/lsp/DocumentSymbolProvider.d.ts +0 -22
  90. package/dist/lsp/DocumentSymbolProvider.js +0 -184
  91. package/dist/lsp/HoverProvider.d.ts +0 -10
  92. package/dist/lsp/HoverProvider.js +0 -36
  93. package/dist/lsp/SemanticTokenProvider.d.ts +0 -8
  94. package/dist/lsp/index.d.ts +0 -7
  95. package/dist/lsp/index.js +0 -6
  96. package/dist/model/fqn-computation.d.ts +0 -4
  97. package/dist/model/fqn-computation.js +0 -43
  98. package/dist/model/fqn-index.d.ts +0 -26
  99. package/dist/model/fqn-index.js +0 -114
  100. package/dist/model/index.d.ts +0 -6
  101. package/dist/model/index.js +0 -5
  102. package/dist/model/model-builder.d.ts +0 -20
  103. package/dist/model/model-builder.js +0 -352
  104. package/dist/model/model-locator.d.ts +0 -22
  105. package/dist/model/model-locator.js +0 -119
  106. package/dist/model/model-parser.d.ts +0 -27
  107. package/dist/model/model-parser.js +0 -410
  108. package/dist/model-change/ModelChanges.d.ts +0 -15
  109. package/dist/model-change/ModelChanges.js +0 -100
  110. package/dist/model-change/changeElementStyle.d.ts +0 -15
  111. package/dist/model-change/changeElementStyle.js +0 -141
  112. package/dist/model-change/changeViewLayout.d.ts +0 -13
  113. package/dist/model-change/changeViewLayout.js +0 -30
  114. package/dist/module.d.ts +0 -59
  115. package/dist/module.js +0 -121
  116. package/dist/node/index.d.ts +0 -6
  117. package/dist/node/index.js +0 -13
  118. package/dist/protocol.d.ts +0 -58
  119. package/dist/protocol.js +0 -14
  120. package/dist/references/index.d.ts +0 -3
  121. package/dist/references/index.js +0 -2
  122. package/dist/references/scope-computation.d.ts +0 -11
  123. package/dist/references/scope-computation.js +0 -108
  124. package/dist/references/scope-provider.d.ts +0 -18
  125. package/dist/references/scope-provider.js +0 -136
  126. package/dist/shared/NodeKindProvider.d.ts +0 -16
  127. package/dist/shared/NodeKindProvider.js +0 -58
  128. package/dist/shared/WorkspaceManager.d.ts +0 -17
  129. package/dist/shared/WorkspaceManager.js +0 -29
  130. package/dist/shared/WorkspaceSymbolProvider.d.ts +0 -4
  131. package/dist/shared/WorkspaceSymbolProvider.js +0 -3
  132. package/dist/shared/index.d.ts +0 -4
  133. package/dist/shared/index.js +0 -3
  134. package/dist/test/index.d.ts +0 -2
  135. package/dist/test/index.js +0 -1
  136. package/dist/test/testServices.d.ts +0 -23
  137. package/dist/test/testServices.js +0 -102
  138. package/dist/utils/index.d.ts +0 -2
  139. package/dist/utils/index.js +0 -1
  140. package/dist/utils/printDocs.d.ts +0 -3
  141. package/dist/utils/printDocs.js +0 -1
  142. package/dist/utils/stringHash.d.ts +0 -2
  143. package/dist/utils/stringHash.js +0 -5
  144. package/dist/validation/element.d.ts +0 -6
  145. package/dist/validation/element.js +0 -38
  146. package/dist/validation/index.d.ts +0 -3
  147. package/dist/validation/property-checks.d.ts +0 -5
  148. package/dist/validation/property-checks.js +0 -11
  149. package/dist/validation/relation.d.ts +0 -5
  150. package/dist/validation/relation.js +0 -50
  151. package/dist/validation/specification.d.ts +0 -10
  152. package/dist/validation/specification.js +0 -97
  153. package/dist/validation/view-predicates/custom-element-expr.d.ts +0 -5
  154. package/dist/validation/view-predicates/custom-element-expr.js +0 -16
  155. package/dist/validation/view-predicates/expanded-element.d.ts +0 -5
  156. package/dist/validation/view-predicates/expanded-element.js +0 -28
  157. package/dist/validation/view-predicates/incoming.d.ts +0 -5
  158. package/dist/validation/view-predicates/incoming.js +0 -14
  159. package/dist/validation/view-predicates/index.d.ts +0 -5
  160. package/dist/validation/view-predicates/index.js +0 -4
  161. package/dist/validation/view-predicates/outgoing.d.ts +0 -5
  162. package/dist/validation/view-predicates/outgoing.js +0 -14
  163. package/dist/validation/view.d.ts +0 -5
  164. package/dist/validation/view.js +0 -18
  165. package/dist/view-utils/assignNavigateTo.d.ts +0 -3
  166. package/dist/view-utils/assignNavigateTo.js +0 -23
  167. package/dist/view-utils/index.d.ts +0 -4
  168. package/dist/view-utils/index.js +0 -3
  169. package/dist/view-utils/resolve-extended-views.d.ts +0 -7
  170. package/dist/view-utils/resolve-extended-views.js +0 -41
  171. package/dist/view-utils/resolve-relative-paths.d.ts +0 -3
  172. package/dist/view-utils/resolve-relative-paths.js +0 -76
  173. /package/{dist → src}/reset.d.ts +0 -0
@@ -1,7 +1,11 @@
1
- import { logger } from "../logger.js";
2
- import { elementChecks } from "./element.js";
3
- import { opacityPropertyRuleChecks } from "./property-checks.js";
4
- import { relationChecks } from "./relation.js";
1
+ import { type ast } from '../ast'
2
+ import { logger } from '../logger'
3
+ import type { LikeC4Services } from '../module'
4
+ import { dynamicViewRulePredicate } from './dynamic-view-rule'
5
+ import { dynamicViewStep } from './dynamic-view-step'
6
+ import { elementChecks } from './element'
7
+ import { opacityPropertyRuleChecks } from './property-checks'
8
+ import { relationChecks } from './relation'
5
9
  import {
6
10
  elementKindChecks,
7
11
  modelRuleChecks,
@@ -9,43 +13,47 @@ import {
9
13
  relationshipChecks,
10
14
  specificationRuleChecks,
11
15
  tagChecks
12
- } from "./specification.js";
13
- import { viewChecks } from "./view.js";
16
+ } from './specification'
17
+ import { viewChecks } from './view'
14
18
  import {
15
19
  customElementExprChecks,
16
20
  expandElementExprChecks,
17
21
  incomingExpressionChecks,
18
22
  outgoingExpressionChecks
19
- } from "./view-predicates/index.js";
20
- export function registerValidationChecks(services) {
21
- logger.info("registerValidationChecks");
22
- const registry = services.validation.ValidationRegistry;
23
- registry.register({
23
+ } from './view-predicates'
24
+
25
+ export function registerValidationChecks(services: LikeC4Services) {
26
+ logger.info('registerValidationChecks')
27
+ const registry = services.validation.ValidationRegistry
28
+ registry.register<ast.LikeC4AstType>({
24
29
  OpacityProperty: opacityPropertyRuleChecks(services),
25
30
  SpecificationRule: specificationRuleChecks(services),
26
31
  Model: modelRuleChecks(services),
27
32
  ModelViews: modelViewsChecks(services),
28
- ElementView: viewChecks(services),
33
+ DynamicViewStep: dynamicViewStep(services),
34
+ LikeC4View: viewChecks(services),
29
35
  Element: elementChecks(services),
30
36
  ElementKind: elementKindChecks(services),
31
37
  Relation: relationChecks(services),
32
38
  Tag: tagChecks(services),
39
+ DynamicViewRulePredicate: dynamicViewRulePredicate(services),
33
40
  CustomElementExpr: customElementExprChecks(services),
34
41
  ExpandElementExpr: expandElementExprChecks(services),
35
42
  RelationshipKind: relationshipChecks(services),
36
43
  IncomingExpr: incomingExpressionChecks(services),
37
44
  OutgoingExpr: outgoingExpressionChecks(services)
38
- });
39
- const connection = services.shared.lsp.Connection;
45
+ })
46
+ const connection = services.shared.lsp.Connection
40
47
  if (connection) {
48
+ // workaround for bug in langium
41
49
  services.shared.workspace.DocumentBuilder.onUpdate((_, deleted) => {
42
50
  for (const uri of deleted) {
43
- logger.debug(`clear diagnostics for deleted ${uri.path}`);
51
+ logger.debug(`clear diagnostics for deleted ${uri.path}`)
44
52
  void connection.sendDiagnostics({
45
53
  uri: uri.toString(),
46
54
  diagnostics: []
47
- });
55
+ })
48
56
  }
49
- });
57
+ })
50
58
  }
51
59
  }
@@ -0,0 +1,17 @@
1
+ import type { ValidationCheck } from 'langium'
2
+ import type { ast } from '../ast'
3
+ import type { LikeC4Services } from '../module'
4
+
5
+ export const opacityPropertyRuleChecks = (
6
+ _: LikeC4Services
7
+ ): ValidationCheck<ast.OpacityProperty> => {
8
+ return (node, accept) => {
9
+ const opacity = parseFloat(node.value)
10
+ if (isNaN(opacity) || opacity < 0 || opacity > 100) {
11
+ accept('warning', `Value ignored, must be between 0% and 100%`, {
12
+ node,
13
+ property: 'value'
14
+ })
15
+ }
16
+ }
17
+ }
@@ -0,0 +1,57 @@
1
+ import { isSameHierarchy } from '@likec4/core'
2
+ import type { ValidationCheck } from 'langium'
3
+ import { ast } from '../ast'
4
+ import { elementRef } from '../elementRef'
5
+ import { logError } from '../logger'
6
+ import type { LikeC4Services } from '../module'
7
+
8
+ export const relationChecks = (services: LikeC4Services): ValidationCheck<ast.Relation> => {
9
+ const fqnIndex = services.likec4.FqnIndex
10
+ return (el, accept) => {
11
+ try {
12
+ const targetEl: ast.Element | undefined = elementRef(el.target)
13
+ const target = targetEl && fqnIndex.getFqn(targetEl)
14
+ if (!target) {
15
+ accept('error', 'Target not found (not parsed/indexed yet)', {
16
+ node: el,
17
+ property: 'target'
18
+ })
19
+ }
20
+ let sourceEl
21
+ if (ast.isExplicitRelation(el)) {
22
+ sourceEl = elementRef(el.source)
23
+ if (!sourceEl) {
24
+ return accept('error', 'Source not found (not parsed/indexed yet)', {
25
+ node: el,
26
+ property: 'source'
27
+ })
28
+ }
29
+ } else {
30
+ sourceEl = el.$container.$container
31
+ }
32
+
33
+ const source = fqnIndex.getFqn(sourceEl)
34
+
35
+ if (!source) {
36
+ accept('error', 'Source not found (not parsed/indexed yet)', {
37
+ node: el
38
+ })
39
+ }
40
+
41
+ if (source && target && isSameHierarchy(source, target)) {
42
+ accept('error', 'Invalid parent-child relationship', {
43
+ node: el
44
+ })
45
+ }
46
+
47
+ if (el.tags?.value && el.body?.tags?.value) {
48
+ accept('error', 'Relation cannot have tags in both header and body', {
49
+ node: el,
50
+ property: 'tags'
51
+ })
52
+ }
53
+ } catch (e) {
54
+ logError(e)
55
+ }
56
+ }
57
+ }
@@ -0,0 +1,118 @@
1
+ import { AstUtils, type ValidationCheck } from 'langium'
2
+ import { ast } from '../ast'
3
+ import type { LikeC4Services } from '../module'
4
+
5
+ export const specificationRuleChecks = (
6
+ _: LikeC4Services
7
+ ): ValidationCheck<ast.SpecificationRule> => {
8
+ return (node, accept) => {
9
+ if (node.$containerIndex && node.$containerIndex > 0) {
10
+ accept('error', `Only one specification per document is allowed`, {
11
+ node: node,
12
+ property: 'name'
13
+ })
14
+ }
15
+ }
16
+ }
17
+
18
+ export const modelRuleChecks = (_: LikeC4Services): ValidationCheck<ast.Model> => {
19
+ return (node, accept) => {
20
+ if (node.$containerIndex && node.$containerIndex > 0) {
21
+ accept('error', `Only one model per document is allowed`, {
22
+ node: node,
23
+ property: 'name'
24
+ })
25
+ }
26
+ }
27
+ }
28
+
29
+ export const modelViewsChecks = (_: LikeC4Services): ValidationCheck<ast.ModelViews> => {
30
+ return (node, accept) => {
31
+ if (node.$containerIndex && node.$containerIndex > 0) {
32
+ accept('error', `Only one views block per document is allowed`, {
33
+ node: node,
34
+ property: 'name'
35
+ })
36
+ }
37
+ }
38
+ }
39
+
40
+ export const elementKindChecks = (services: LikeC4Services): ValidationCheck<ast.ElementKind> => {
41
+ const index = services.shared.workspace.IndexManager
42
+ return (node, accept) => {
43
+ const sameKind = index
44
+ .allElements(ast.ElementKind)
45
+ .filter(n => n.name === node.name && n.node !== node)
46
+ .head()
47
+ if (sameKind) {
48
+ const isAnotherDoc = sameKind.documentUri !== AstUtils.getDocument(node).uri
49
+ accept('error', `Duplicate element kind '${node.name}'`, {
50
+ node: node,
51
+ property: 'name',
52
+ ...isAnotherDoc && {
53
+ relatedInformation: [
54
+ {
55
+ location: {
56
+ range: sameKind.nameSegment!.range,
57
+ uri: sameKind.documentUri.toString()
58
+ },
59
+ message: `conflicting definition`
60
+ }
61
+ ]
62
+ }
63
+ })
64
+ }
65
+ }
66
+ }
67
+
68
+ export const tagChecks = (services: LikeC4Services): ValidationCheck<ast.Tag> => {
69
+ const index = services.shared.workspace.IndexManager
70
+ return (node, accept) => {
71
+ const tagname = '#' + node.name
72
+ const sameTag = index
73
+ .allElements(ast.Tag)
74
+ .filter(n => n.name === tagname && n.node !== node)
75
+ .head()
76
+ if (sameTag) {
77
+ const isAnotherDoc = sameTag.documentUri !== AstUtils.getDocument(node).uri
78
+ accept(
79
+ 'error',
80
+ `Duplicate tag '${node.name}'`,
81
+ {
82
+ node,
83
+ property: 'name',
84
+ ...isAnotherDoc && {
85
+ relatedInformation: [
86
+ {
87
+ location: {
88
+ range: sameTag.nameSegment!.range,
89
+ uri: sameTag.documentUri.toString()
90
+ },
91
+ message: `conflicting definition`
92
+ }
93
+ ]
94
+ }
95
+ }
96
+ )
97
+ }
98
+ }
99
+ }
100
+
101
+ export const relationshipChecks = (
102
+ services: LikeC4Services
103
+ ): ValidationCheck<ast.RelationshipKind> => {
104
+ const index = services.shared.workspace.IndexManager
105
+ return (node, accept) => {
106
+ const sameKinds = index
107
+ .allElements(ast.RelationshipKind)
108
+ .filter(n => n.name === node.name)
109
+ .limit(2)
110
+ .count()
111
+ if (sameKinds > 1) {
112
+ accept('error', `Duplicate RelationshipKind '${node.name}'`, {
113
+ node: node,
114
+ property: 'name'
115
+ })
116
+ }
117
+ }
118
+ }
@@ -0,0 +1,21 @@
1
+ import type { ValidationCheck } from 'langium'
2
+ import { ast } from '../../ast'
3
+ import type { LikeC4Services } from '../../module'
4
+
5
+ export const customElementExprChecks = (
6
+ _services: LikeC4Services
7
+ ): ValidationCheck<ast.CustomElementExpr> => {
8
+ return (el, accept) => {
9
+ if (ast.isExcludePredicate(el.$container)) {
10
+ accept('error', 'Invalid inside "exclude"', {
11
+ node: el
12
+ })
13
+ }
14
+ if (!ast.isElementRef(el.target)) {
15
+ accept('error', 'Invalid target for customization', {
16
+ node: el,
17
+ property: 'target'
18
+ })
19
+ }
20
+ }
21
+ }
@@ -0,0 +1,34 @@
1
+ import { nonexhaustive } from '@likec4/core'
2
+ import type { ValidationCheck } from 'langium'
3
+ import { ast } from '../../ast'
4
+ import type { LikeC4Services } from '../../module'
5
+
6
+ export const expandElementExprChecks = (
7
+ _services: LikeC4Services
8
+ ): ValidationCheck<ast.ExpandElementExpr> => {
9
+ return (el, accept) => {
10
+ switch (true) {
11
+ case ast.isIncludePredicate(el.$container):
12
+ case ast.isDynamicViewRulePredicate(el.$container):
13
+ case ast.isViewRuleStyle(el.$container):
14
+ return
15
+ case ast.isCustomElementExpr(el.$container):
16
+ return accept('warning', `Custom rules apply only to parent`, {
17
+ node: el
18
+ })
19
+ case ast.isExcludePredicate(el.$container):
20
+ return accept('warning', `Ignored, as can't be used in exclude`, {
21
+ node: el
22
+ })
23
+ case ast.isInOutExpr(el.$container):
24
+ case ast.isIncomingExpr(el.$container):
25
+ case ast.isOutgoingExpr(el.$container):
26
+ case ast.isRelationExpr(el.$container):
27
+ return accept('warning', `Wrong usage of expanded element in relations predicate`, {
28
+ node: el
29
+ })
30
+ default:
31
+ nonexhaustive(el.$container)
32
+ }
33
+ }
34
+ }
@@ -0,0 +1,19 @@
1
+ import type { ValidationCheck } from 'langium'
2
+ import { isNullish } from 'remeda'
3
+ import { ast } from '../../ast'
4
+ import type { LikeC4Services } from '../../module'
5
+
6
+ export const incomingExpressionChecks = (
7
+ _services: LikeC4Services
8
+ ): ValidationCheck<ast.IncomingExpr> => {
9
+ return (el, accept) => {
10
+ if (ast.isWildcardExpr(el.to) && ast.isViewRulePredicate(el.$container)) {
11
+ const view = el.$container.$container.$container
12
+ if (isNullish(view.viewOf)) {
13
+ accept('warning', 'Predicate is ignored as it concerns all relationships', {
14
+ node: el
15
+ })
16
+ }
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,4 @@
1
+ export * from './custom-element-expr'
2
+ export * from './expanded-element'
3
+ export * from './incoming'
4
+ export * from './outgoing'
@@ -0,0 +1,19 @@
1
+ import type { ValidationCheck } from 'langium'
2
+ import { isNullish } from 'remeda'
3
+ import { ast } from '../../ast'
4
+ import type { LikeC4Services } from '../../module'
5
+
6
+ export const outgoingExpressionChecks = (
7
+ _services: LikeC4Services
8
+ ): ValidationCheck<ast.OutgoingExpr> => {
9
+ return (el, accept) => {
10
+ if (ast.isWildcardExpr(el.from)) {
11
+ const view = el.$container.$container.$container
12
+ if (view.$type === 'ElementView' && isNullish(view.viewOf)) {
13
+ accept('warning', 'Predicate is ignored as it concerns all relationships', {
14
+ node: el
15
+ })
16
+ }
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,26 @@
1
+ import type { ValidationCheck } from 'langium'
2
+ import { ast } from '../ast'
3
+ import type { LikeC4Services } from '../module'
4
+
5
+ export const viewChecks = (services: LikeC4Services): ValidationCheck<ast.LikeC4View> => {
6
+ const index = services.shared.workspace.IndexManager
7
+ return (el, accept) => {
8
+ // if (el.extends) {
9
+ // // TODO: circular dependency check
10
+ // }
11
+ if (!el.name) {
12
+ return
13
+ }
14
+ const anotherViews = index
15
+ .allElements(ast.LikeC4View)
16
+ .filter(n => n.name === el.name)
17
+ .limit(2)
18
+ .count()
19
+ if (anotherViews > 1) {
20
+ accept('error', `Duplicate view '${el.name}'`, {
21
+ node: el,
22
+ property: 'name'
23
+ })
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,30 @@
1
+ import { type ComputedView, type Fqn, isComputedElementView, type ViewID } from '@likec4/core'
2
+ import { find } from 'remeda'
3
+
4
+ export function assignNavigateTo<R extends Iterable<ComputedView>>(views: R): R {
5
+ const allElementViews = new Map<Fqn, ViewID[]>()
6
+
7
+ for (const v of views) {
8
+ if (isComputedElementView(v) && v.viewOf && !v.extends) {
9
+ const viewsOf = allElementViews.get(v.viewOf) ?? []
10
+ viewsOf.push(v.id)
11
+ allElementViews.set(v.viewOf, viewsOf)
12
+ }
13
+ }
14
+
15
+ // set default navigateTo
16
+ for (const { id, nodes } of views) {
17
+ for (const node of nodes) {
18
+ if (node.navigateTo) {
19
+ continue
20
+ }
21
+ // find first element view that is not the current one
22
+ const navigateTo = find(allElementViews.get(node.id) ?? [], v => v !== id)
23
+ if (navigateTo) {
24
+ node.navigateTo = navigateTo
25
+ }
26
+ }
27
+ }
28
+
29
+ return views
30
+ }
@@ -0,0 +1,3 @@
1
+ export * from './assignNavigateTo'
2
+ export * from './resolve-extended-views'
3
+ export * from './resolve-relative-paths'
@@ -0,0 +1,57 @@
1
+ import graphlib from '@dagrejs/graphlib'
2
+ import { isExtendsElementView, type View } from '@likec4/core'
3
+
4
+ // '@dagrejs/graphlib' is a CommonJS module
5
+ // Here is a workaround to import it
6
+ const { Graph, alg } = graphlib
7
+
8
+ /**
9
+ * Resolve rules of extended views
10
+ * (Removes invalid views)
11
+ */
12
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
+ export function resolveRulesExtendedViews<V extends Record<any, View>>(
14
+ unresolvedViews: V
15
+ ): V {
16
+ const g = new Graph({
17
+ directed: true,
18
+ multigraph: false,
19
+ compound: false
20
+ })
21
+ for (const view of Object.values(unresolvedViews)) {
22
+ g.setNode(view.id)
23
+ if (isExtendsElementView(view)) {
24
+ // view -> parent
25
+ g.setEdge(view.id, view.extends)
26
+ }
27
+ }
28
+
29
+ // Remove circular dependencies
30
+ const cycles = alg.findCycles(g)
31
+ if (cycles.length > 0) {
32
+ cycles.flat().forEach(id => g.removeNode(id))
33
+ }
34
+
35
+ const ordered = alg.postorder(g, g.sources())
36
+
37
+ return ordered.reduce((acc, id) => {
38
+ const view = unresolvedViews[id]
39
+ if (!view) {
40
+ return acc
41
+ }
42
+ if (isExtendsElementView(view)) {
43
+ const extendsFrom = acc[view.extends]
44
+ if (!extendsFrom) {
45
+ return acc
46
+ }
47
+ return Object.assign(acc, {
48
+ [view.id]: {
49
+ ...extendsFrom,
50
+ ...view,
51
+ rules: [...extendsFrom.rules, ...view.rules]
52
+ }
53
+ })
54
+ }
55
+ return Object.assign(acc, { [view.id]: view })
56
+ }, {} as V)
57
+ }
@@ -0,0 +1,84 @@
1
+ import type { View } from '@likec4/core'
2
+ import { invariant } from '@likec4/core'
3
+ import { hasAtLeast, unique, zip } from 'remeda'
4
+
5
+ function commonAncestorPath(views: View[], sep = '/') {
6
+ if (views.length <= 1) return ''
7
+ const uniqURIs = unique(views.flatMap(({ docUri }) => (docUri ? [docUri] : [])))
8
+ if (uniqURIs.length === 0) return ''
9
+ if (uniqURIs.length === 1) {
10
+ invariant(hasAtLeast(uniqURIs, 1))
11
+ return new URL(uniqURIs[0]).pathname
12
+ }
13
+ invariant(hasAtLeast(uniqURIs, 2), 'Expected at least 2 unique URIs')
14
+ const [baseUri, ...tail] = uniqURIs
15
+ const parts = new URL(baseUri).pathname.split(sep)
16
+ let endOfPrefix = parts.length
17
+ for (const uri of tail) {
18
+ if (uri === baseUri) {
19
+ continue
20
+ }
21
+ const compare = new URL(uri).pathname.split(sep)
22
+ for (let i = 0; i < endOfPrefix; i++) {
23
+ if (compare[i] !== parts[i]) {
24
+ endOfPrefix = i
25
+ }
26
+ }
27
+ if (endOfPrefix === 0) return ''
28
+ }
29
+ const prefix = parts.slice(0, endOfPrefix).join(sep)
30
+ return prefix.endsWith(sep) ? prefix : prefix + sep
31
+ }
32
+
33
+ export function resolveRelativePaths(views: View[]): View[] {
34
+ const commonPrefix = commonAncestorPath(views)
35
+ return (
36
+ views
37
+ // For each view, compute the relative path to the common prefix
38
+ // Store array of path segments
39
+ .map(view => {
40
+ if (!view.docUri) {
41
+ return {
42
+ ...view,
43
+ parts: []
44
+ }
45
+ }
46
+ const path = new URL(view.docUri).pathname
47
+ const parts = path.replace(commonPrefix, '').split('/')
48
+ parts.pop() // remove filename
49
+ return {
50
+ ...view,
51
+ parts
52
+ }
53
+ })
54
+ // Sort views by path segments
55
+ .sort((a, b) => {
56
+ if (a.parts.length === b.parts.length) {
57
+ if (a.parts.length === 0) {
58
+ return 0
59
+ }
60
+ if (a.parts.length === 1 && hasAtLeast(a.parts, 1) && hasAtLeast(b.parts, 1)) {
61
+ return a.parts[0].localeCompare(b.parts[0])
62
+ }
63
+ for (const [_a, _b] of zip(a.parts, b.parts)) {
64
+ const compare = _a.localeCompare(_b)
65
+ if (compare !== 0) {
66
+ return compare
67
+ }
68
+ }
69
+ return 0
70
+ }
71
+ return a.parts.length - b.parts.length
72
+ })
73
+ // Build relativePath from path segments
74
+ .map(({ parts, ...view }) => {
75
+ if (view.docUri) {
76
+ return {
77
+ ...view,
78
+ relativePath: parts.join('/')
79
+ }
80
+ }
81
+ return view
82
+ })
83
+ )
84
+ }
package/dist/Rpc.d.ts DELETED
@@ -1,10 +0,0 @@
1
- import type { LikeC4Services } from './module';
2
- import { Disposable } from 'langium';
3
- export declare class Rpc implements Disposable {
4
- private services;
5
- private disposables;
6
- constructor(services: LikeC4Services);
7
- init(): void;
8
- dispose(): void;
9
- }
10
- //# sourceMappingURL=Rpc.d.ts.map