@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.
- package/package.json +8 -10
- package/src/Rpc.ts +108 -0
- package/src/ast.ts +443 -0
- package/src/browser/index.ts +30 -0
- package/src/elementRef.ts +26 -0
- package/src/generated/ast.ts +1632 -0
- package/src/generated/grammar.ts +10 -0
- package/src/generated/module.ts +32 -0
- package/src/index.ts +4 -0
- package/src/like-c4.langium +395 -0
- package/src/logger.ts +54 -0
- package/src/lsp/CodeLensProvider.ts +51 -0
- package/src/lsp/DocumentHighlightProvider.ts +12 -0
- package/src/lsp/DocumentLinkProvider.test.ts +66 -0
- package/src/lsp/DocumentLinkProvider.ts +53 -0
- package/src/lsp/DocumentSymbolProvider.ts +201 -0
- package/src/lsp/HoverProvider.ts +58 -0
- package/{dist/lsp/SemanticTokenProvider.js → src/lsp/SemanticTokenProvider.ts} +57 -42
- package/src/lsp/index.ts +6 -0
- package/src/model/fqn-computation.ts +47 -0
- package/src/model/fqn-index.ts +161 -0
- package/src/model/index.ts +5 -0
- package/src/model/model-builder.ts +447 -0
- package/src/model/model-locator.ts +130 -0
- package/src/model/model-parser.ts +580 -0
- package/src/model-change/ModelChanges.ts +120 -0
- package/src/model-change/changeElementStyle.ts +176 -0
- package/src/model-change/changeViewLayout.ts +41 -0
- package/src/module.ts +197 -0
- package/src/node/index.ts +20 -0
- package/src/protocol.ts +87 -0
- package/src/references/index.ts +2 -0
- package/src/references/scope-computation.ts +142 -0
- package/src/references/scope-provider.ts +166 -0
- package/src/shared/NodeKindProvider.ts +67 -0
- package/src/shared/WorkspaceManager.ts +39 -0
- package/src/shared/WorkspaceSymbolProvider.ts +3 -0
- package/src/shared/index.ts +3 -0
- package/src/test/index.ts +1 -0
- package/src/test/testServices.ts +119 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/printDocs.ts +3 -0
- package/src/utils/stringHash.ts +6 -0
- package/{dist/validation/dynamic-view-rule.js → src/validation/dynamic-view-rule.ts} +14 -11
- package/src/validation/dynamic-view-step.ts +39 -0
- package/src/validation/element.ts +52 -0
- package/{dist/validation/index.js → src/validation/index.ts} +22 -18
- package/src/validation/property-checks.ts +17 -0
- package/src/validation/relation.ts +57 -0
- package/src/validation/specification.ts +118 -0
- package/src/validation/view-predicates/custom-element-expr.ts +21 -0
- package/{dist/validation/view-predicates/expanded-element.js → src/validation/view-predicates/expanded-element.ts} +18 -13
- package/src/validation/view-predicates/incoming.ts +19 -0
- package/src/validation/view-predicates/index.ts +4 -0
- package/src/validation/view-predicates/outgoing.ts +19 -0
- package/src/validation/view.ts +26 -0
- package/src/view-utils/assignNavigateTo.ts +30 -0
- package/src/view-utils/index.ts +3 -0
- package/src/view-utils/resolve-extended-views.ts +57 -0
- package/src/view-utils/resolve-relative-paths.ts +84 -0
- package/dist/Rpc.d.ts +0 -10
- package/dist/Rpc.js +0 -98
- package/dist/ast.d.ts +0 -149
- package/dist/ast.js +0 -271
- package/dist/browser/index.d.ts +0 -9
- package/dist/browser/index.js +0 -16
- package/dist/elementRef.d.ts +0 -12
- package/dist/elementRef.js +0 -15
- package/dist/generated/ast.d.ts +0 -615
- package/dist/generated/ast.js +0 -957
- package/dist/generated/grammar.d.ts +0 -7
- package/dist/generated/grammar.js +0 -3
- package/dist/generated/module.d.ts +0 -14
- package/dist/generated/module.js +0 -22
- package/dist/index.d.ts +0 -5
- package/dist/index.js +0 -2
- package/dist/logger.d.ts +0 -12
- package/dist/logger.js +0 -51
- package/dist/lsp/CodeLensProvider.d.ts +0 -10
- package/dist/lsp/CodeLensProvider.js +0 -40
- package/dist/lsp/DocumentHighlightProvider.d.ts +0 -10
- package/dist/lsp/DocumentHighlightProvider.js +0 -10
- package/dist/lsp/DocumentLinkProvider.d.ts +0 -11
- package/dist/lsp/DocumentLinkProvider.js +0 -41
- package/dist/lsp/DocumentLinkProvider.test.d.ts +0 -2
- package/dist/lsp/DocumentLinkProvider.test.js +0 -54
- package/dist/lsp/DocumentSymbolProvider.d.ts +0 -22
- package/dist/lsp/DocumentSymbolProvider.js +0 -189
- package/dist/lsp/HoverProvider.d.ts +0 -10
- package/dist/lsp/HoverProvider.js +0 -36
- package/dist/lsp/SemanticTokenProvider.d.ts +0 -8
- package/dist/lsp/index.d.ts +0 -7
- package/dist/lsp/index.js +0 -6
- package/dist/model/fqn-computation.d.ts +0 -4
- package/dist/model/fqn-computation.js +0 -43
- package/dist/model/fqn-index.d.ts +0 -26
- package/dist/model/fqn-index.js +0 -114
- package/dist/model/index.d.ts +0 -6
- package/dist/model/index.js +0 -5
- package/dist/model/model-builder.d.ts +0 -20
- package/dist/model/model-builder.js +0 -365
- package/dist/model/model-locator.d.ts +0 -22
- package/dist/model/model-locator.js +0 -115
- package/dist/model/model-parser.d.ts +0 -29
- package/dist/model/model-parser.js +0 -520
- package/dist/model-change/ModelChanges.d.ts +0 -16
- package/dist/model-change/ModelChanges.js +0 -106
- package/dist/model-change/changeElementStyle.d.ts +0 -18
- package/dist/model-change/changeElementStyle.js +0 -141
- package/dist/model-change/changeViewLayout.d.ts +0 -13
- package/dist/model-change/changeViewLayout.js +0 -29
- package/dist/module.d.ts +0 -59
- package/dist/module.js +0 -121
- package/dist/node/index.d.ts +0 -6
- package/dist/node/index.js +0 -13
- package/dist/protocol.d.ts +0 -58
- package/dist/protocol.js +0 -14
- package/dist/references/index.d.ts +0 -3
- package/dist/references/index.js +0 -2
- package/dist/references/scope-computation.d.ts +0 -11
- package/dist/references/scope-computation.js +0 -111
- package/dist/references/scope-provider.d.ts +0 -18
- package/dist/references/scope-provider.js +0 -136
- package/dist/shared/NodeKindProvider.d.ts +0 -16
- package/dist/shared/NodeKindProvider.js +0 -60
- package/dist/shared/WorkspaceManager.d.ts +0 -17
- package/dist/shared/WorkspaceManager.js +0 -29
- package/dist/shared/WorkspaceSymbolProvider.d.ts +0 -4
- package/dist/shared/WorkspaceSymbolProvider.js +0 -3
- package/dist/shared/index.d.ts +0 -4
- package/dist/shared/index.js +0 -3
- package/dist/test/index.d.ts +0 -2
- package/dist/test/index.js +0 -1
- package/dist/test/testServices.d.ts +0 -23
- package/dist/test/testServices.js +0 -102
- package/dist/utils/index.d.ts +0 -2
- package/dist/utils/index.js +0 -1
- package/dist/utils/printDocs.d.ts +0 -3
- package/dist/utils/printDocs.js +0 -1
- package/dist/utils/stringHash.d.ts +0 -2
- package/dist/utils/stringHash.js +0 -5
- package/dist/validation/dynamic-view-rule.d.ts +0 -5
- package/dist/validation/dynamic-view-step.d.ts +0 -5
- package/dist/validation/dynamic-view-step.js +0 -33
- package/dist/validation/element.d.ts +0 -6
- package/dist/validation/element.js +0 -38
- package/dist/validation/index.d.ts +0 -3
- package/dist/validation/property-checks.d.ts +0 -5
- package/dist/validation/property-checks.js +0 -11
- package/dist/validation/relation.d.ts +0 -5
- package/dist/validation/relation.js +0 -50
- package/dist/validation/specification.d.ts +0 -10
- package/dist/validation/specification.js +0 -97
- package/dist/validation/view-predicates/custom-element-expr.d.ts +0 -5
- package/dist/validation/view-predicates/custom-element-expr.js +0 -16
- package/dist/validation/view-predicates/expanded-element.d.ts +0 -5
- package/dist/validation/view-predicates/incoming.d.ts +0 -5
- package/dist/validation/view-predicates/incoming.js +0 -14
- package/dist/validation/view-predicates/index.d.ts +0 -5
- package/dist/validation/view-predicates/index.js +0 -4
- package/dist/validation/view-predicates/outgoing.d.ts +0 -5
- package/dist/validation/view-predicates/outgoing.js +0 -14
- package/dist/validation/view.d.ts +0 -5
- package/dist/validation/view.js +0 -16
- package/dist/view-utils/assignNavigateTo.d.ts +0 -3
- package/dist/view-utils/assignNavigateTo.js +0 -24
- package/dist/view-utils/index.d.ts +0 -4
- package/dist/view-utils/index.js +0 -3
- package/dist/view-utils/resolve-extended-views.d.ts +0 -7
- package/dist/view-utils/resolve-extended-views.js +0 -41
- package/dist/view-utils/resolve-relative-paths.d.ts +0 -3
- package/dist/view-utils/resolve-relative-paths.js +0 -75
- /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 @@
|
|
|
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'
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import { nonexhaustive } from
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
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(
|
|
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
|
+
}
|