@likec4/language-server 1.2.0 → 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/package.json +8 -10
  2. package/src/Rpc.ts +108 -0
  3. package/src/ast.ts +443 -0
  4. package/src/browser/index.ts +30 -0
  5. package/src/elementRef.ts +26 -0
  6. package/src/generated/ast.ts +1632 -0
  7. package/src/generated/grammar.ts +10 -0
  8. package/src/generated/module.ts +32 -0
  9. package/src/index.ts +4 -0
  10. package/src/like-c4.langium +395 -0
  11. package/src/logger.ts +54 -0
  12. package/src/lsp/CodeLensProvider.ts +51 -0
  13. package/src/lsp/DocumentHighlightProvider.ts +12 -0
  14. package/src/lsp/DocumentLinkProvider.test.ts +66 -0
  15. package/src/lsp/DocumentLinkProvider.ts +53 -0
  16. package/src/lsp/DocumentSymbolProvider.ts +201 -0
  17. package/src/lsp/HoverProvider.ts +58 -0
  18. package/{dist/lsp/SemanticTokenProvider.js → src/lsp/SemanticTokenProvider.ts} +57 -42
  19. package/src/lsp/index.ts +6 -0
  20. package/src/model/fqn-computation.ts +47 -0
  21. package/src/model/fqn-index.ts +161 -0
  22. package/src/model/index.ts +5 -0
  23. package/src/model/model-builder.ts +447 -0
  24. package/src/model/model-locator.ts +130 -0
  25. package/src/model/model-parser.ts +580 -0
  26. package/src/model-change/ModelChanges.ts +120 -0
  27. package/src/model-change/changeElementStyle.ts +176 -0
  28. package/src/model-change/changeViewLayout.ts +41 -0
  29. package/src/module.ts +197 -0
  30. package/src/node/index.ts +20 -0
  31. package/src/protocol.ts +87 -0
  32. package/src/references/index.ts +2 -0
  33. package/src/references/scope-computation.ts +142 -0
  34. package/src/references/scope-provider.ts +166 -0
  35. package/src/shared/NodeKindProvider.ts +67 -0
  36. package/src/shared/WorkspaceManager.ts +39 -0
  37. package/src/shared/WorkspaceSymbolProvider.ts +3 -0
  38. package/src/shared/index.ts +3 -0
  39. package/src/test/index.ts +1 -0
  40. package/src/test/testServices.ts +119 -0
  41. package/src/utils/index.ts +1 -0
  42. package/src/utils/printDocs.ts +3 -0
  43. package/src/utils/stringHash.ts +6 -0
  44. package/{dist/validation/dynamic-view-rule.js → src/validation/dynamic-view-rule.ts} +14 -11
  45. package/src/validation/dynamic-view-step.ts +39 -0
  46. package/src/validation/element.ts +52 -0
  47. package/{dist/validation/index.js → src/validation/index.ts} +22 -18
  48. package/src/validation/property-checks.ts +17 -0
  49. package/src/validation/relation.ts +57 -0
  50. package/src/validation/specification.ts +118 -0
  51. package/src/validation/view-predicates/custom-element-expr.ts +21 -0
  52. package/{dist/validation/view-predicates/expanded-element.js → src/validation/view-predicates/expanded-element.ts} +18 -13
  53. package/src/validation/view-predicates/incoming.ts +19 -0
  54. package/src/validation/view-predicates/index.ts +4 -0
  55. package/src/validation/view-predicates/outgoing.ts +19 -0
  56. package/src/validation/view.ts +26 -0
  57. package/src/view-utils/assignNavigateTo.ts +30 -0
  58. package/src/view-utils/index.ts +3 -0
  59. package/src/view-utils/resolve-extended-views.ts +57 -0
  60. package/src/view-utils/resolve-relative-paths.ts +84 -0
  61. package/dist/Rpc.d.ts +0 -10
  62. package/dist/Rpc.js +0 -98
  63. package/dist/ast.d.ts +0 -149
  64. package/dist/ast.js +0 -271
  65. package/dist/browser/index.d.ts +0 -9
  66. package/dist/browser/index.js +0 -16
  67. package/dist/elementRef.d.ts +0 -12
  68. package/dist/elementRef.js +0 -15
  69. package/dist/generated/ast.d.ts +0 -615
  70. package/dist/generated/ast.js +0 -957
  71. package/dist/generated/grammar.d.ts +0 -7
  72. package/dist/generated/grammar.js +0 -3
  73. package/dist/generated/module.d.ts +0 -14
  74. package/dist/generated/module.js +0 -22
  75. package/dist/index.d.ts +0 -5
  76. package/dist/index.js +0 -2
  77. package/dist/logger.d.ts +0 -12
  78. package/dist/logger.js +0 -51
  79. package/dist/lsp/CodeLensProvider.d.ts +0 -10
  80. package/dist/lsp/CodeLensProvider.js +0 -40
  81. package/dist/lsp/DocumentHighlightProvider.d.ts +0 -10
  82. package/dist/lsp/DocumentHighlightProvider.js +0 -10
  83. package/dist/lsp/DocumentLinkProvider.d.ts +0 -11
  84. package/dist/lsp/DocumentLinkProvider.js +0 -41
  85. package/dist/lsp/DocumentLinkProvider.test.d.ts +0 -2
  86. package/dist/lsp/DocumentLinkProvider.test.js +0 -54
  87. package/dist/lsp/DocumentSymbolProvider.d.ts +0 -22
  88. package/dist/lsp/DocumentSymbolProvider.js +0 -189
  89. package/dist/lsp/HoverProvider.d.ts +0 -10
  90. package/dist/lsp/HoverProvider.js +0 -36
  91. package/dist/lsp/SemanticTokenProvider.d.ts +0 -8
  92. package/dist/lsp/index.d.ts +0 -7
  93. package/dist/lsp/index.js +0 -6
  94. package/dist/model/fqn-computation.d.ts +0 -4
  95. package/dist/model/fqn-computation.js +0 -43
  96. package/dist/model/fqn-index.d.ts +0 -26
  97. package/dist/model/fqn-index.js +0 -114
  98. package/dist/model/index.d.ts +0 -6
  99. package/dist/model/index.js +0 -5
  100. package/dist/model/model-builder.d.ts +0 -20
  101. package/dist/model/model-builder.js +0 -365
  102. package/dist/model/model-locator.d.ts +0 -22
  103. package/dist/model/model-locator.js +0 -115
  104. package/dist/model/model-parser.d.ts +0 -29
  105. package/dist/model/model-parser.js +0 -520
  106. package/dist/model-change/ModelChanges.d.ts +0 -16
  107. package/dist/model-change/ModelChanges.js +0 -106
  108. package/dist/model-change/changeElementStyle.d.ts +0 -18
  109. package/dist/model-change/changeElementStyle.js +0 -141
  110. package/dist/model-change/changeViewLayout.d.ts +0 -13
  111. package/dist/model-change/changeViewLayout.js +0 -29
  112. package/dist/module.d.ts +0 -59
  113. package/dist/module.js +0 -121
  114. package/dist/node/index.d.ts +0 -6
  115. package/dist/node/index.js +0 -13
  116. package/dist/protocol.d.ts +0 -58
  117. package/dist/protocol.js +0 -14
  118. package/dist/references/index.d.ts +0 -3
  119. package/dist/references/index.js +0 -2
  120. package/dist/references/scope-computation.d.ts +0 -11
  121. package/dist/references/scope-computation.js +0 -111
  122. package/dist/references/scope-provider.d.ts +0 -18
  123. package/dist/references/scope-provider.js +0 -136
  124. package/dist/shared/NodeKindProvider.d.ts +0 -16
  125. package/dist/shared/NodeKindProvider.js +0 -60
  126. package/dist/shared/WorkspaceManager.d.ts +0 -17
  127. package/dist/shared/WorkspaceManager.js +0 -29
  128. package/dist/shared/WorkspaceSymbolProvider.d.ts +0 -4
  129. package/dist/shared/WorkspaceSymbolProvider.js +0 -3
  130. package/dist/shared/index.d.ts +0 -4
  131. package/dist/shared/index.js +0 -3
  132. package/dist/test/index.d.ts +0 -2
  133. package/dist/test/index.js +0 -1
  134. package/dist/test/testServices.d.ts +0 -23
  135. package/dist/test/testServices.js +0 -102
  136. package/dist/utils/index.d.ts +0 -2
  137. package/dist/utils/index.js +0 -1
  138. package/dist/utils/printDocs.d.ts +0 -3
  139. package/dist/utils/printDocs.js +0 -1
  140. package/dist/utils/stringHash.d.ts +0 -2
  141. package/dist/utils/stringHash.js +0 -5
  142. package/dist/validation/dynamic-view-rule.d.ts +0 -5
  143. package/dist/validation/dynamic-view-step.d.ts +0 -5
  144. package/dist/validation/dynamic-view-step.js +0 -33
  145. package/dist/validation/element.d.ts +0 -6
  146. package/dist/validation/element.js +0 -38
  147. package/dist/validation/index.d.ts +0 -3
  148. package/dist/validation/property-checks.d.ts +0 -5
  149. package/dist/validation/property-checks.js +0 -11
  150. package/dist/validation/relation.d.ts +0 -5
  151. package/dist/validation/relation.js +0 -50
  152. package/dist/validation/specification.d.ts +0 -10
  153. package/dist/validation/specification.js +0 -97
  154. package/dist/validation/view-predicates/custom-element-expr.d.ts +0 -5
  155. package/dist/validation/view-predicates/custom-element-expr.js +0 -16
  156. package/dist/validation/view-predicates/expanded-element.d.ts +0 -5
  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 -16
  165. package/dist/view-utils/assignNavigateTo.d.ts +0 -3
  166. package/dist/view-utils/assignNavigateTo.js +0 -24
  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 -75
  173. /package/{dist → src}/reset.d.ts +0 -0
@@ -0,0 +1,142 @@
1
+ import {
2
+ type AstNode,
3
+ type AstNodeDescription,
4
+ DefaultScopeComputation,
5
+ MultiMap,
6
+ type PrecomputedScopes
7
+ } from 'langium'
8
+ import { isTruthy } from 'remeda'
9
+ import type { CancellationToken } from 'vscode-languageserver'
10
+ import { ast, type LikeC4LangiumDocument } from '../ast'
11
+ import { logError } from '../logger'
12
+
13
+ type ElementsContainer = ast.Model | ast.ElementBody | ast.ExtendElementBody
14
+
15
+ export class LikeC4ScopeComputation extends DefaultScopeComputation {
16
+ override async computeExports(
17
+ document: LikeC4LangiumDocument,
18
+ _cancelToken?: CancellationToken
19
+ ): Promise<AstNodeDescription[]> {
20
+ const docExports: AstNodeDescription[] = []
21
+ try {
22
+ const { specifications, models, views } = document.parseResult.value
23
+
24
+ // Process specification
25
+ for (
26
+ const spec of specifications.flatMap(s => [
27
+ ...s.elements,
28
+ ...s.relationships,
29
+ ...s.tags
30
+ ])
31
+ ) {
32
+ try {
33
+ if (ast.isSpecificationTag(spec)) {
34
+ if (spec.tag && isTruthy(spec.tag.name)) {
35
+ docExports.push(
36
+ this.descriptions.createDescription(spec.tag, '#' + spec.tag.name, document)
37
+ )
38
+ }
39
+ continue
40
+ }
41
+ if (spec.kind && isTruthy(spec.kind.name)) {
42
+ docExports.push(
43
+ this.descriptions.createDescription(spec.kind, spec.kind.name, document)
44
+ )
45
+ }
46
+ } catch (e) {
47
+ logError(e)
48
+ }
49
+ }
50
+
51
+ // Process models
52
+ for (const elAst of models.flatMap(m => m.elements)) {
53
+ try {
54
+ if (ast.isElement(elAst) && isTruthy(elAst.name)) {
55
+ docExports.push(this.descriptions.createDescription(elAst, elAst.name, document))
56
+ }
57
+ } catch (e) {
58
+ logError(e)
59
+ }
60
+ }
61
+
62
+ // Process views
63
+ for (const viewAst of views.flatMap(v => v.views)) {
64
+ try {
65
+ if (isTruthy(viewAst.name)) {
66
+ docExports.push(this.descriptions.createDescription(viewAst, viewAst.name, document))
67
+ }
68
+ } catch (e) {
69
+ logError(e)
70
+ }
71
+ }
72
+ } catch (e) {
73
+ logError(e)
74
+ }
75
+ return docExports
76
+ }
77
+
78
+ override computeLocalScopes(
79
+ document: LikeC4LangiumDocument,
80
+ _cancelToken?: CancellationToken
81
+ ): Promise<PrecomputedScopes> {
82
+ return new Promise(resolve => {
83
+ const root = document.parseResult.value
84
+ const scopes = new MultiMap<AstNode, AstNodeDescription>()
85
+ for (const model of root.models) {
86
+ try {
87
+ const nested = this.processContainer(model, scopes, document)
88
+ scopes.addAll(root, nested.values())
89
+ } catch (e) {
90
+ logError(e)
91
+ }
92
+ }
93
+ resolve(scopes)
94
+ })
95
+ }
96
+
97
+ protected processContainer(
98
+ container: ElementsContainer,
99
+ scopes: PrecomputedScopes,
100
+ document: LikeC4LangiumDocument
101
+ ) {
102
+ const localScope = new MultiMap<string, AstNodeDescription>()
103
+ const nestedScopes = new MultiMap<string, AstNodeDescription>()
104
+ for (const el of container.elements) {
105
+ if (ast.isRelation(el)) {
106
+ continue
107
+ }
108
+
109
+ let subcontainer
110
+ if (ast.isElement(el)) {
111
+ if (isTruthy(el.name)) {
112
+ localScope.add(el.name, this.descriptions.createDescription(el, el.name, document))
113
+ }
114
+ subcontainer = el.body
115
+ } else if (ast.isExtendElement(el)) {
116
+ subcontainer = el.body
117
+ }
118
+
119
+ if (subcontainer && subcontainer.elements.length > 0) {
120
+ try {
121
+ const nested = this.processContainer(subcontainer, scopes, document)
122
+ for (const [nestedName, desc] of nested) {
123
+ nestedScopes.add(nestedName, desc)
124
+ }
125
+ } catch (e) {
126
+ logError(e)
127
+ }
128
+ }
129
+ }
130
+
131
+ if (nestedScopes.size > 0) {
132
+ for (const [name, descriptions] of nestedScopes.entriesGroupedByKey()) {
133
+ // If name is unique for current scope
134
+ if (!localScope.has(name) && descriptions.length === 1) {
135
+ localScope.add(name, descriptions[0]!)
136
+ }
137
+ }
138
+ }
139
+ scopes.addAll(container, localScope.values())
140
+ return localScope
141
+ }
142
+ }
@@ -0,0 +1,166 @@
1
+ import type { likec4 as c4 } from '@likec4/core'
2
+ import type { AstNode } from 'langium'
3
+ import {
4
+ type AstNodeDescription,
5
+ AstUtils,
6
+ CstUtils,
7
+ DefaultScopeProvider,
8
+ DONE_RESULT,
9
+ EMPTY_STREAM,
10
+ GrammarUtils,
11
+ type ReferenceInfo,
12
+ type Scope,
13
+ type Stream,
14
+ stream,
15
+ StreamImpl,
16
+ StreamScope
17
+ } from 'langium'
18
+ import { ast } from '../ast'
19
+ import { elementRef, getFqnElementRef } from '../elementRef'
20
+ import { logError } from '../logger'
21
+ import type { FqnIndex, FqnIndexEntry } from '../model/fqn-index'
22
+ import type { LikeC4Services } from '../module'
23
+
24
+ const { findNodeForProperty } = GrammarUtils
25
+ const { toDocumentSegment } = CstUtils
26
+ const { getDocument } = AstUtils
27
+
28
+ function toAstNodeDescription(entry: FqnIndexEntry): AstNodeDescription {
29
+ const $cstNode = findNodeForProperty(entry.el.$cstNode, 'name')
30
+ return {
31
+ documentUri: entry.doc.uri,
32
+ name: entry.name,
33
+ ...(entry.el.$cstNode && {
34
+ selectionSegment: toDocumentSegment(entry.el.$cstNode)
35
+ }),
36
+ ...($cstNode && {
37
+ nameSegment: toDocumentSegment($cstNode)
38
+ }),
39
+ path: entry.path,
40
+ type: ast.Element
41
+ }
42
+ }
43
+
44
+ export class LikeC4ScopeProvider extends DefaultScopeProvider {
45
+ private fqnIndex: FqnIndex
46
+
47
+ constructor(services: LikeC4Services) {
48
+ super(services)
49
+ this.fqnIndex = services.likec4.FqnIndex
50
+ }
51
+
52
+ private directChildrenOf(parent: c4.Fqn): Stream<AstNodeDescription> {
53
+ return this.fqnIndex.directChildrenOf(parent).map(toAstNodeDescription)
54
+ }
55
+
56
+ // we need lazy resolving here
57
+ private uniqueDescedants(of: () => ast.Element | undefined): Stream<AstNodeDescription> {
58
+ return new StreamImpl(
59
+ () => {
60
+ const element = of()
61
+ const fqn = element && this.fqnIndex.getFqn(element)
62
+ if (fqn) {
63
+ return this.fqnIndex.uniqueDescedants(fqn).map(toAstNodeDescription).iterator()
64
+ }
65
+ return null
66
+ },
67
+ iterator => {
68
+ if (iterator) {
69
+ return iterator.next()
70
+ }
71
+ return DONE_RESULT
72
+ }
73
+ )
74
+ }
75
+
76
+ private scopeElementRef(ref: ast.ElementRef): Stream<AstNodeDescription> {
77
+ return this.uniqueDescedants(() => ref.el.ref)
78
+ }
79
+
80
+ private scopeExtendElement({ element }: ast.ExtendElement): Stream<AstNodeDescription> {
81
+ // we make extended element resolvable inside ExtendElementBody
82
+ return stream([element.el.$nodeDescription])
83
+ .nonNullable()
84
+ .concat(this.uniqueDescedants(() => elementRef(element)))
85
+ }
86
+
87
+ private scopeElementView({ viewOf, extends: ext }: ast.ElementView): Stream<AstNodeDescription> {
88
+ if (viewOf) {
89
+ // If we have "view of parent.target"
90
+ // we make "target" resolvable inside ElementView
91
+ return stream([viewOf.el.$nodeDescription])
92
+ .nonNullable()
93
+ .concat(this.uniqueDescedants(() => elementRef(viewOf)))
94
+ }
95
+ if (ext) {
96
+ return stream([ext]).flatMap(v => {
97
+ const view = v.view.ref
98
+ return view ? this.scopeElementView(view) : EMPTY_STREAM
99
+ })
100
+ }
101
+ return EMPTY_STREAM
102
+ }
103
+
104
+ override getScope(context: ReferenceInfo): Scope {
105
+ const referenceType = this.reflection.getReferenceType(context)
106
+ try {
107
+ const container = context.container
108
+ if (ast.isFqnElementRef(container) && context.property === 'el') {
109
+ const parent = container.parent
110
+ if (!parent) {
111
+ return this.getGlobalScope(referenceType)
112
+ }
113
+ return new StreamScope(this.directChildrenOf(getFqnElementRef(parent)))
114
+ }
115
+ if (ast.isElementRef(container) && context.property === 'el') {
116
+ const parent = container.parent
117
+ if (parent) {
118
+ return new StreamScope(this.scopeElementRef(parent))
119
+ }
120
+ }
121
+ return this.computeScope(context)
122
+ } catch (e) {
123
+ logError(e)
124
+ return this.getGlobalScope(referenceType)
125
+ }
126
+ }
127
+
128
+ protected computeScope(context: ReferenceInfo) {
129
+ const referenceType = this.reflection.getReferenceType(context)
130
+ const scopes: Stream<AstNodeDescription>[] = []
131
+ const doc = getDocument(context.container)
132
+ const precomputed = doc.precomputedScopes
133
+
134
+ if (precomputed) {
135
+ const byReferenceType = (desc: AstNodeDescription) => this.reflection.isSubtype(desc.type, referenceType)
136
+
137
+ let container: AstNode | undefined = context.container
138
+ while (container) {
139
+ const elements = precomputed.get(container).filter(byReferenceType)
140
+ if (elements.length > 0) {
141
+ scopes.push(stream(elements))
142
+ }
143
+ if (referenceType === ast.Element) {
144
+ if (ast.isExtendElementBody(container)) {
145
+ scopes.push(this.scopeExtendElement(container.$container))
146
+ }
147
+ if (ast.isElementViewBody(container)) {
148
+ scopes.push(this.scopeElementView(container.$container))
149
+ }
150
+ }
151
+ container = container.$container
152
+ }
153
+ }
154
+
155
+ return scopes.reduceRight((outerScope, elements) => {
156
+ return this.createScope(elements, outerScope)
157
+ }, this.getGlobalScope(referenceType))
158
+ }
159
+
160
+ /**
161
+ * Create a global scope filtered for the given reference type.
162
+ */
163
+ protected override getGlobalScope(referenceType: string): Scope {
164
+ return new StreamScope(this.indexManager.allElements(referenceType))
165
+ }
166
+ }
@@ -0,0 +1,67 @@
1
+ import { type AstNode, type AstNodeDescription } from 'langium'
2
+ import type { LangiumSharedServices, NodeKindProvider as LspNodeKindProvider } from 'langium/lsp'
3
+ import { CompletionItemKind, SymbolKind } from 'vscode-languageserver-protocol'
4
+ import { ast } from '../ast'
5
+
6
+ export class NodeKindProvider implements LspNodeKindProvider {
7
+ constructor(private services: LangiumSharedServices) {}
8
+
9
+ /**
10
+ * Returns a `SymbolKind` as used by `WorkspaceSymbolProvider` or `DocumentSymbolProvider`.
11
+ */
12
+ // prettier-ignore
13
+ getSymbolKind(node: AstNode | AstNodeDescription): SymbolKind {
14
+ const hasType = (type: string) => 'type' in node && this.services.AstReflection.isSubtype(node.type, type)
15
+ switch (true) {
16
+ case (ast.isElement(node) || hasType(ast.Element))
17
+ || (ast.isExtendElement(node) || hasType(ast.ExtendElement)): {
18
+ return SymbolKind.Constructor
19
+ }
20
+ case ast.isModel(node) || ast.isModelViews(node) || ast.isSpecificationRule(node)
21
+ || hasType(ast.Model) || hasType(ast.ModelViews) || hasType(ast.SpecificationRule): {
22
+ return SymbolKind.Namespace
23
+ }
24
+ case (ast.isLikeC4View(node) || hasType(ast.LikeC4View)): {
25
+ return SymbolKind.Class
26
+ }
27
+ case (ast.isTag(node) || hasType(ast.Tag))
28
+ || (ast.isSpecificationTag(node) || hasType(ast.SpecificationTag)): {
29
+ return SymbolKind.EnumMember
30
+ }
31
+ case (ast.isRelationshipKind(node) || hasType(ast.RelationshipKind))
32
+ || (ast.isSpecificationRelationshipKind(node) || hasType(ast.SpecificationRelationshipKind)): {
33
+ return SymbolKind.Event
34
+ }
35
+ case (ast.isElementKind(node) || hasType(ast.ElementKind))
36
+ || (ast.isSpecificationElementKind(node) || hasType(ast.SpecificationElementKind)): {
37
+ return SymbolKind.TypeParameter
38
+ }
39
+ }
40
+ return SymbolKind.Constant
41
+ }
42
+ /**
43
+ * Returns a `CompletionItemKind` as used by the `CompletionProvider`.
44
+ */
45
+ getCompletionItemKind(node: AstNode | AstNodeDescription): CompletionItemKind {
46
+ switch (this.getSymbolKind(node)) {
47
+ case SymbolKind.Constructor:
48
+ return CompletionItemKind.Constructor
49
+ case SymbolKind.Namespace:
50
+ return CompletionItemKind.Module
51
+ case SymbolKind.Class:
52
+ return CompletionItemKind.Class
53
+ case SymbolKind.Enum:
54
+ return CompletionItemKind.Enum
55
+ case SymbolKind.EnumMember:
56
+ return CompletionItemKind.EnumMember
57
+ case SymbolKind.TypeParameter:
58
+ return CompletionItemKind.TypeParameter
59
+ case SymbolKind.Interface:
60
+ return CompletionItemKind.Interface
61
+ case SymbolKind.Event:
62
+ return CompletionItemKind.Event
63
+ default:
64
+ return CompletionItemKind.Reference
65
+ }
66
+ }
67
+ }
@@ -0,0 +1,39 @@
1
+ import { hasAtLeast, invariant } from '@likec4/core'
2
+ import type { LangiumDocument } from 'langium'
3
+ import { DefaultWorkspaceManager } from 'langium'
4
+ import type { WorkspaceFolder } from 'vscode-languageserver'
5
+ import { URI } from 'vscode-uri'
6
+
7
+ export class LikeC4WorkspaceManager extends DefaultWorkspaceManager {
8
+ /**
9
+ * Load all additional documents that shall be visible in the context of the given workspace
10
+ * folders and add them to the collector. This can be used to include built-in libraries of
11
+ * your language, which can be either loaded from provided files or constructed in memory.
12
+ */
13
+ protected override loadAdditionalDocuments(
14
+ _folders: WorkspaceFolder[],
15
+ _collector: (document: LangiumDocument) => void
16
+ ): Promise<void> {
17
+ // collector(this.documentFactory.fromString(builtin.specification.document, URI.parse(builtin.specification.uri)))
18
+ return Promise.resolve()
19
+ }
20
+
21
+ public workspace() {
22
+ if (this.folders && hasAtLeast(this.folders, 1)) {
23
+ return this.folders[0]
24
+ }
25
+ return null
26
+ }
27
+
28
+ public get workspaceUri() {
29
+ const workspace = this.workspace()
30
+ invariant(workspace, 'Workspace not initialized')
31
+ return URI.parse(workspace.uri)
32
+ }
33
+
34
+ public get workspaceURL() {
35
+ const workspace = this.workspace()
36
+ invariant(workspace, 'Workspace not initialized')
37
+ return new URL(workspace.uri)
38
+ }
39
+ }
@@ -0,0 +1,3 @@
1
+ import { DefaultWorkspaceSymbolProvider } from 'langium/lsp'
2
+
3
+ export class WorkspaceSymbolProvider extends DefaultWorkspaceSymbolProvider {}
@@ -0,0 +1,3 @@
1
+ export * from './NodeKindProvider'
2
+ export * from './WorkspaceManager'
3
+ export * from './WorkspaceSymbolProvider'
@@ -0,0 +1 @@
1
+ export * from './testServices'
@@ -0,0 +1,119 @@
1
+ import { DocumentState, EmptyFileSystem } from 'langium'
2
+ import * as assert from 'node:assert'
3
+ import stripIndent from 'strip-indent'
4
+ import { type Diagnostic, DiagnosticSeverity } from 'vscode-languageserver-protocol'
5
+ import { URI, Utils } from 'vscode-uri'
6
+ import type { LikeC4LangiumDocument } from '../ast'
7
+ import { createLanguageServices } from '../module'
8
+
9
+ export function createTestServices(workspace = 'file:///test/workspace') {
10
+ const services = createLanguageServices(EmptyFileSystem).likec4
11
+ const metaData = services.LanguageMetaData
12
+ const langiumDocuments = services.shared.workspace.LangiumDocuments
13
+ const documentBuilder = services.shared.workspace.DocumentBuilder
14
+ const modelBuilder = services.likec4.ModelBuilder
15
+ const workspaceUri = URI.parse(workspace)
16
+ const workspaceFolder = {
17
+ name: 'test',
18
+ uri: workspaceUri.toString()
19
+ }
20
+ let isInitialized = false
21
+ let documentIndex = 1
22
+
23
+ const parse = async (input: string, uri?: string) => {
24
+ if (!isInitialized) {
25
+ await services.shared.workspace.WorkspaceLock.write(async (_cancelToken) => {
26
+ if (isInitialized) {
27
+ return
28
+ }
29
+ isInitialized = true
30
+ await services.shared.workspace.WorkspaceManager.initializeWorkspace([workspaceFolder])
31
+ // Workaround to set protected folders property
32
+ Object.assign(services.shared.workspace.WorkspaceManager, {
33
+ folders: [workspaceFolder]
34
+ })
35
+ })
36
+ }
37
+ const docUri = Utils.resolvePath(
38
+ workspaceUri,
39
+ './src/',
40
+ uri ?? `${documentIndex++}${metaData.fileExtensions[0]}`
41
+ )
42
+ const document = services.shared.workspace.LangiumDocumentFactory.fromString(
43
+ stripIndent(input),
44
+ docUri
45
+ )
46
+ langiumDocuments.addDocument(document)
47
+ await services.shared.workspace.WorkspaceLock.write(async (_cancelToken) => {
48
+ await documentBuilder.build([document], { validation: false })
49
+ })
50
+ return document as LikeC4LangiumDocument
51
+ }
52
+
53
+ const validate = async (input: string | LikeC4LangiumDocument, uri?: string) => {
54
+ const document = typeof input === 'string' ? await parse(input, uri) : input
55
+ await services.shared.workspace.WorkspaceLock.write(async (_cancelToken) => {
56
+ await documentBuilder.build([document], { validation: true })
57
+ })
58
+ const diagnostics = document.diagnostics ?? []
59
+ const warnings = diagnostics.flatMap(d => d.severity === DiagnosticSeverity.Warning ? d.message : [])
60
+ const errors = diagnostics.flatMap(d => d.severity === DiagnosticSeverity.Error ? d.message : [])
61
+ return {
62
+ document,
63
+ diagnostics,
64
+ warnings,
65
+ errors
66
+ }
67
+ }
68
+
69
+ type ValidateAllResult = {
70
+ diagnostics: Diagnostic[]
71
+ errors: string[]
72
+ warnings: string[]
73
+ }
74
+ let previousPromise = Promise.resolve() as Promise<any>
75
+ const validateAll = async () => {
76
+ await services.shared.workspace.WorkspaceLock.write(async (_cancelToken) => {
77
+ const docs = langiumDocuments.all.toArray()
78
+ await documentBuilder.build(docs, { validation: true })
79
+ })
80
+ await documentBuilder.waitUntil(DocumentState.Validated)
81
+ const docs = langiumDocuments.all.toArray()
82
+ assert.ok(docs.length > 0, 'no documents to validate')
83
+ const diagnostics = docs.flatMap(doc => doc.diagnostics ?? [])
84
+ const warnings = diagnostics.flatMap(d => d.severity === DiagnosticSeverity.Warning ? d.message : [])
85
+ const errors = diagnostics.flatMap(d => d.severity === DiagnosticSeverity.Error ? d.message : [])
86
+ return {
87
+ diagnostics,
88
+ errors,
89
+ warnings
90
+ }
91
+ }
92
+
93
+ const buildModel = async () => {
94
+ await validateAll()
95
+ const model = await modelBuilder.buildComputedModel()
96
+ if (!model) throw new Error('No model found')
97
+ return model
98
+ }
99
+
100
+ const resetState = async () => {
101
+ await services.shared.workspace.WorkspaceLock.write(async (cancelToken) => {
102
+ const docs = langiumDocuments.all.toArray().map(doc => doc.uri)
103
+ await documentBuilder.update([], docs, cancelToken)
104
+ })
105
+ }
106
+
107
+ return {
108
+ services,
109
+ parse,
110
+ validate,
111
+ validateAll,
112
+ buildModel,
113
+ resetState
114
+ }
115
+ }
116
+
117
+ export type TestServices = ReturnType<typeof createTestServices>
118
+ export type TestParseFn = TestServices['validate']
119
+ export type TestValidateFn = TestServices['validate']
@@ -0,0 +1 @@
1
+ export * from './stringHash'
@@ -0,0 +1,3 @@
1
+ import type { LangiumDocument } from 'langium'
2
+
3
+ export const printDocs = (docs: LangiumDocument[]) => docs.map(d => ' - ' + d.uri.toString(true)).join('\n')
@@ -0,0 +1,6 @@
1
+ import hash from 'string-hash'
2
+
3
+ export function stringHash(...str: [string, ...string[]]): string {
4
+ var s = str.length > 1 ? str.join(':::') : str[0]
5
+ return hash(s).toString(36)
6
+ }
@@ -1,7 +1,10 @@
1
- import { nonexhaustive } from "@likec4/core";
2
- import { ast } from "../ast.js";
3
- import { logError } from "../logger.js";
4
- export const dynamicViewRulePredicate = (_services) => {
1
+ import { nonexhaustive } from '@likec4/core'
2
+ import type { ValidationCheck } from 'langium'
3
+ import { ast } from '../ast'
4
+ import { logError } from '../logger'
5
+ import type { LikeC4Services } from '../module'
6
+
7
+ export const dynamicViewRulePredicate = (_services: LikeC4Services): ValidationCheck<ast.DynamicViewRulePredicate> => {
5
8
  return (el, accept) => {
6
9
  try {
7
10
  for (const expr of el.expressions) {
@@ -10,7 +13,7 @@ export const dynamicViewRulePredicate = (_services) => {
10
13
  case ast.isDescedantsExpr(expr):
11
14
  case ast.isCustomElementExpr(expr):
12
15
  case ast.isExpandElementExpr(expr):
13
- return;
16
+ return
14
17
  case ast.isRelationExpr(expr):
15
18
  case ast.isInOutExpr(expr):
16
19
  case ast.isIncomingExpr(expr):
@@ -18,15 +21,15 @@ export const dynamicViewRulePredicate = (_services) => {
18
21
  case ast.isElementKindExpr(expr):
19
22
  case ast.isElementTagExpr(expr):
20
23
  case ast.isWildcardExpr(expr):
21
- return accept("warning", `Expression is not supported by dynamic views`, {
24
+ return accept('warning', `Expression is not supported by dynamic views`, {
22
25
  node: expr
23
- });
26
+ })
24
27
  default:
25
- nonexhaustive(expr);
28
+ nonexhaustive(expr)
26
29
  }
27
30
  }
28
31
  } catch (e) {
29
- logError(e);
32
+ logError(e)
30
33
  }
31
- };
32
- };
34
+ }
35
+ }
@@ -0,0 +1,39 @@
1
+ import { isAncestor } 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 dynamicViewStep = (services: LikeC4Services): ValidationCheck<ast.DynamicViewStep> => {
9
+ const fqnIndex = services.likec4.FqnIndex
10
+ return (el, accept) => {
11
+ try {
12
+ const sourceEl: ast.Element | undefined = elementRef(el.source)
13
+ const source = sourceEl && fqnIndex.getFqn(sourceEl)
14
+ if (!source) {
15
+ accept('error', 'Source not found (not parsed/indexed yet)', {
16
+ node: el,
17
+ property: 'source'
18
+ })
19
+ }
20
+
21
+ const targetEl: ast.Element | undefined = elementRef(el.target)
22
+ const target = targetEl && fqnIndex.getFqn(targetEl)
23
+ if (!target) {
24
+ accept('error', 'Target not found (not parsed/indexed yet)', {
25
+ node: el,
26
+ property: 'target'
27
+ })
28
+ }
29
+
30
+ if (source && target && (isAncestor(source, target) || isAncestor(target, source))) {
31
+ accept('error', 'Invalid parent-child relationship', {
32
+ node: el
33
+ })
34
+ }
35
+ } catch (e) {
36
+ logError(e)
37
+ }
38
+ }
39
+ }