@likec4/language-server 1.4.0 → 1.6.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.
- package/README.md +1 -1
- package/contrib/likec4.tmLanguage.json +1 -1
- package/package.json +26 -14
- package/src/Rpc.ts +25 -2
- package/src/ast.ts +19 -15
- package/src/generated/ast.ts +390 -203
- package/src/generated/grammar.ts +1 -1
- package/src/generated-lib/icons.ts +952 -0
- package/src/like-c4.langium +120 -64
- package/src/likec4lib.ts +7 -0
- package/src/lsp/DocumentSymbolProvider.ts +28 -1
- package/src/lsp/SemanticTokenProvider.ts +41 -22
- package/src/model/fqn-computation.ts +29 -7
- package/src/model/fqn-index.ts +18 -31
- package/src/model/model-builder.ts +13 -9
- package/src/model/model-locator.ts +7 -7
- package/src/model/model-parser.ts +166 -69
- package/src/model-change/changeElementStyle.ts +6 -6
- package/src/model-graph/compute-view/__test__/fixture.ts +52 -24
- package/src/model-graph/compute-view/compute.ts +51 -20
- package/src/model-graph/compute-view/predicates.ts +6 -1
- package/src/model-graph/dynamic-view/__test__/fixture.ts +2 -2
- package/src/model-graph/dynamic-view/compute.ts +2 -2
- package/src/model-graph/utils/{applyElementCustomProperties.ts → applyCustomElementProperties.ts} +5 -3
- package/src/model-graph/utils/applyCustomRelationProperties.ts +50 -0
- package/src/model-graph/utils/applyViewRuleStyles.ts +11 -34
- package/src/model-graph/utils/elementExpressionToPredicate.ts +32 -0
- package/src/references/scope-computation.ts +113 -60
- package/src/references/scope-provider.ts +3 -23
- package/src/shared/NodeKindProvider.ts +1 -0
- package/src/shared/WorkspaceManager.ts +15 -6
- package/src/validation/dynamic-view-rule.ts +19 -26
- package/src/validation/element.ts +8 -4
- package/src/validation/index.ts +9 -6
- package/src/validation/property-checks.ts +23 -1
- package/src/validation/view-predicates/custom-element-expr.ts +21 -8
- package/src/validation/view-predicates/custom-relation-expr.ts +16 -0
- package/src/validation/view-predicates/expanded-element.ts +13 -24
- package/src/validation/view-predicates/incoming.ts +5 -5
- package/src/validation/view-predicates/index.ts +1 -0
- package/src/validation/view-predicates/outgoing.ts +5 -5
- package/src/view-utils/assignNavigateTo.ts +2 -2
- package/src/view-utils/manual-layout.ts +4 -2
- package/src/view-utils/resolve-extended-views.ts +2 -2
- package/src/view-utils/resolve-relative-paths.ts +3 -3
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
MultiMap,
|
|
7
7
|
type PrecomputedScopes
|
|
8
8
|
} from 'langium'
|
|
9
|
-
import { isTruthy } from 'remeda'
|
|
9
|
+
import { isNullish, isTruthy } from 'remeda'
|
|
10
10
|
import type { CancellationToken } from 'vscode-languageserver'
|
|
11
11
|
import { ast, type LikeC4LangiumDocument } from '../ast'
|
|
12
12
|
import { logError } from '../logger'
|
|
@@ -20,77 +20,130 @@ export class LikeC4ScopeComputation extends DefaultScopeComputation {
|
|
|
20
20
|
): Promise<AstNodeDescription[]> {
|
|
21
21
|
const docExports: AstNodeDescription[] = []
|
|
22
22
|
try {
|
|
23
|
-
const { specifications, models, views } = document.parseResult.value
|
|
23
|
+
const { specifications, models, views, likec4lib } = document.parseResult.value
|
|
24
|
+
|
|
25
|
+
// Process library
|
|
26
|
+
this.exportLibrary(likec4lib, docExports, document)
|
|
24
27
|
|
|
25
28
|
// Process specification
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
case ast.isSpecificationRelationshipKind(spec): {
|
|
52
|
-
if (isTruthy(spec.kind.name)) {
|
|
53
|
-
docExports.push(
|
|
54
|
-
this.descriptions.createDescription(spec.kind, spec.kind.name, document),
|
|
55
|
-
this.descriptions.createDescription(spec.kind, '.' + spec.kind.name, document)
|
|
56
|
-
)
|
|
57
|
-
}
|
|
58
|
-
continue
|
|
59
|
-
}
|
|
60
|
-
// Thow error if not exhaustive
|
|
61
|
-
default:
|
|
62
|
-
nonexhaustive(spec)
|
|
63
|
-
}
|
|
64
|
-
} catch (e) {
|
|
65
|
-
logError(e)
|
|
29
|
+
this.exportSpecification(specifications, docExports, document)
|
|
30
|
+
|
|
31
|
+
// Process models
|
|
32
|
+
this.exportModel(models, docExports, document)
|
|
33
|
+
|
|
34
|
+
// Process views
|
|
35
|
+
this.exportViews(views, docExports, document)
|
|
36
|
+
} catch (e) {
|
|
37
|
+
logError(e)
|
|
38
|
+
}
|
|
39
|
+
return docExports
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private exportViews(
|
|
43
|
+
views: ast.ModelViews[] | undefined,
|
|
44
|
+
docExports: AstNodeDescription[],
|
|
45
|
+
document: LikeC4LangiumDocument
|
|
46
|
+
) {
|
|
47
|
+
if (isNullish(views) || views.length === 0) {
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
for (const viewAst of views.flatMap(v => v.views)) {
|
|
51
|
+
try {
|
|
52
|
+
if (isTruthy(viewAst.name)) {
|
|
53
|
+
docExports.push(this.descriptions.createDescription(viewAst, viewAst.name, document))
|
|
66
54
|
}
|
|
55
|
+
} catch (e) {
|
|
56
|
+
logError(e)
|
|
67
57
|
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
68
60
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
61
|
+
private exportModel(
|
|
62
|
+
models: ast.Model[] | undefined,
|
|
63
|
+
docExports: AstNodeDescription[],
|
|
64
|
+
document: LikeC4LangiumDocument
|
|
65
|
+
) {
|
|
66
|
+
if (isNullish(models) || models.length === 0) {
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
for (const elAst of models.flatMap(m => m.elements)) {
|
|
70
|
+
try {
|
|
71
|
+
if (ast.isElement(elAst) && isTruthy(elAst.name)) {
|
|
72
|
+
docExports.push(this.descriptions.createDescription(elAst, elAst.name, document))
|
|
77
73
|
}
|
|
74
|
+
} catch (e) {
|
|
75
|
+
logError(e)
|
|
78
76
|
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
80
|
+
private exportLibrary(
|
|
81
|
+
likec4lib: ast.LikeC4Lib[] | undefined,
|
|
82
|
+
docExports: AstNodeDescription[],
|
|
83
|
+
document: LikeC4LangiumDocument
|
|
84
|
+
) {
|
|
85
|
+
if (isNullish(likec4lib) || likec4lib.length === 0) {
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
for (const iconAst of likec4lib.flatMap(l => l.icons)) {
|
|
89
|
+
try {
|
|
90
|
+
docExports.push(this.descriptions.createDescription(iconAst, iconAst.name, document))
|
|
91
|
+
} catch (e) {
|
|
92
|
+
logError(e)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private exportSpecification(
|
|
98
|
+
specifications: ast.SpecificationRule[] | undefined,
|
|
99
|
+
docExports: AstNodeDescription[],
|
|
100
|
+
document: LikeC4LangiumDocument
|
|
101
|
+
) {
|
|
102
|
+
if (isNullish(specifications) || specifications.length === 0) {
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
for (
|
|
106
|
+
const spec of specifications.flatMap(s => [
|
|
107
|
+
...s.elements,
|
|
108
|
+
...s.relationships,
|
|
109
|
+
...s.tags
|
|
110
|
+
])
|
|
111
|
+
) {
|
|
112
|
+
try {
|
|
113
|
+
switch (true) {
|
|
114
|
+
case ast.isSpecificationElementKind(spec): {
|
|
115
|
+
if (isTruthy(spec.kind.name)) {
|
|
116
|
+
docExports.push(
|
|
117
|
+
this.descriptions.createDescription(spec.kind, spec.kind.name, document)
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
continue
|
|
85
121
|
}
|
|
86
|
-
|
|
87
|
-
|
|
122
|
+
case ast.isSpecificationTag(spec): {
|
|
123
|
+
if (isTruthy(spec.tag.name)) {
|
|
124
|
+
docExports.push(
|
|
125
|
+
this.descriptions.createDescription(spec.tag, '#' + spec.tag.name, document)
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
continue
|
|
129
|
+
}
|
|
130
|
+
case ast.isSpecificationRelationshipKind(spec): {
|
|
131
|
+
if (isTruthy(spec.kind.name)) {
|
|
132
|
+
docExports.push(
|
|
133
|
+
this.descriptions.createDescription(spec.kind, spec.kind.name, document),
|
|
134
|
+
this.descriptions.createDescription(spec.kind, '.' + spec.kind.name, document)
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
continue
|
|
138
|
+
}
|
|
139
|
+
// Thow error if not exhaustive
|
|
140
|
+
default:
|
|
141
|
+
nonexhaustive(spec)
|
|
88
142
|
}
|
|
143
|
+
} catch (e) {
|
|
144
|
+
logError(e)
|
|
89
145
|
}
|
|
90
|
-
} catch (e) {
|
|
91
|
-
logError(e)
|
|
92
146
|
}
|
|
93
|
-
return docExports
|
|
94
147
|
}
|
|
95
148
|
|
|
96
149
|
override computeLocalScopes(
|
|
@@ -3,12 +3,10 @@ import type { AstNode } from 'langium'
|
|
|
3
3
|
import {
|
|
4
4
|
type AstNodeDescription,
|
|
5
5
|
AstUtils,
|
|
6
|
-
CstUtils,
|
|
7
6
|
DefaultScopeProvider,
|
|
8
7
|
DONE_RESULT,
|
|
9
8
|
EMPTY_SCOPE,
|
|
10
9
|
EMPTY_STREAM,
|
|
11
|
-
GrammarUtils,
|
|
12
10
|
type ReferenceInfo,
|
|
13
11
|
type Scope,
|
|
14
12
|
type Stream,
|
|
@@ -19,29 +17,11 @@ import {
|
|
|
19
17
|
import { ast } from '../ast'
|
|
20
18
|
import { elementRef, getFqnElementRef } from '../elementRef'
|
|
21
19
|
import { logger } from '../logger'
|
|
22
|
-
import type { FqnIndex
|
|
20
|
+
import type { FqnIndex } from '../model/fqn-index'
|
|
23
21
|
import type { LikeC4Services } from '../module'
|
|
24
22
|
|
|
25
|
-
const { findNodeForProperty } = GrammarUtils
|
|
26
|
-
const { toDocumentSegment } = CstUtils
|
|
27
23
|
const { getDocument } = AstUtils
|
|
28
24
|
|
|
29
|
-
function toAstNodeDescription(entry: FqnIndexEntry): AstNodeDescription {
|
|
30
|
-
const $cstNode = findNodeForProperty(entry.el.$cstNode, 'name')
|
|
31
|
-
return {
|
|
32
|
-
documentUri: entry.doc.uri,
|
|
33
|
-
name: entry.name,
|
|
34
|
-
...(entry.el.$cstNode && {
|
|
35
|
-
selectionSegment: toDocumentSegment(entry.el.$cstNode)
|
|
36
|
-
}),
|
|
37
|
-
...($cstNode && {
|
|
38
|
-
nameSegment: toDocumentSegment($cstNode)
|
|
39
|
-
}),
|
|
40
|
-
path: entry.path,
|
|
41
|
-
type: ast.Element
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
25
|
export class LikeC4ScopeProvider extends DefaultScopeProvider {
|
|
46
26
|
private fqnIndex: FqnIndex
|
|
47
27
|
|
|
@@ -51,7 +31,7 @@ export class LikeC4ScopeProvider extends DefaultScopeProvider {
|
|
|
51
31
|
}
|
|
52
32
|
|
|
53
33
|
private directChildrenOf(parent: c4.Fqn): Stream<AstNodeDescription> {
|
|
54
|
-
return this.fqnIndex.directChildrenOf(parent)
|
|
34
|
+
return this.fqnIndex.directChildrenOf(parent)
|
|
55
35
|
}
|
|
56
36
|
|
|
57
37
|
// we need lazy resolving here
|
|
@@ -61,7 +41,7 @@ export class LikeC4ScopeProvider extends DefaultScopeProvider {
|
|
|
61
41
|
const element = of()
|
|
62
42
|
const fqn = element && this.fqnIndex.getFqn(element)
|
|
63
43
|
if (fqn) {
|
|
64
|
-
return this.fqnIndex.uniqueDescedants(fqn).
|
|
44
|
+
return this.fqnIndex.uniqueDescedants(fqn).iterator()
|
|
65
45
|
}
|
|
66
46
|
return null
|
|
67
47
|
},
|
|
@@ -25,6 +25,7 @@ export class NodeKindProvider implements LspNodeKindProvider {
|
|
|
25
25
|
return SymbolKind.Class
|
|
26
26
|
}
|
|
27
27
|
case (ast.isTag(node) || hasType(ast.Tag))
|
|
28
|
+
|| (ast.isLibIcon(node) || hasType(ast.LibIcon))
|
|
28
29
|
|| (ast.isSpecificationTag(node) || hasType(ast.SpecificationTag)): {
|
|
29
30
|
return SymbolKind.EnumMember
|
|
30
31
|
}
|
|
@@ -1,21 +1,30 @@
|
|
|
1
1
|
import { hasAtLeast, invariant } from '@likec4/core'
|
|
2
|
-
import type { LangiumDocument } from 'langium'
|
|
2
|
+
import type { LangiumDocument, LangiumDocumentFactory } from 'langium'
|
|
3
3
|
import { DefaultWorkspaceManager } from 'langium'
|
|
4
|
+
import type { LangiumSharedServices } from 'langium/lsp'
|
|
4
5
|
import type { WorkspaceFolder } from 'vscode-languageserver'
|
|
5
6
|
import { URI } from 'vscode-uri'
|
|
7
|
+
import * as BuiltIn from '../likec4lib'
|
|
6
8
|
|
|
7
9
|
export class LikeC4WorkspaceManager extends DefaultWorkspaceManager {
|
|
10
|
+
private documentFactory: LangiumDocumentFactory
|
|
11
|
+
|
|
12
|
+
constructor(services: LangiumSharedServices) {
|
|
13
|
+
super(services)
|
|
14
|
+
this.documentFactory = services.workspace.LangiumDocumentFactory
|
|
15
|
+
}
|
|
16
|
+
|
|
8
17
|
/**
|
|
9
18
|
* Load all additional documents that shall be visible in the context of the given workspace
|
|
10
19
|
* folders and add them to the collector. This can be used to include built-in libraries of
|
|
11
20
|
* your language, which can be either loaded from provided files or constructed in memory.
|
|
12
21
|
*/
|
|
13
|
-
protected override loadAdditionalDocuments(
|
|
14
|
-
|
|
15
|
-
|
|
22
|
+
protected override async loadAdditionalDocuments(
|
|
23
|
+
folders: WorkspaceFolder[],
|
|
24
|
+
collector: (document: LangiumDocument) => void
|
|
16
25
|
): Promise<void> {
|
|
17
|
-
|
|
18
|
-
|
|
26
|
+
await super.loadAdditionalDocuments(folders, collector)
|
|
27
|
+
collector(this.documentFactory.fromString(BuiltIn.Content, URI.parse(BuiltIn.Uri)))
|
|
19
28
|
}
|
|
20
29
|
|
|
21
30
|
public workspace() {
|
|
@@ -1,35 +1,28 @@
|
|
|
1
1
|
import { nonexhaustive } from '@likec4/core'
|
|
2
2
|
import type { ValidationCheck } from 'langium'
|
|
3
3
|
import { ast } from '../ast'
|
|
4
|
-
import { logError } from '../logger'
|
|
5
4
|
import type { LikeC4Services } from '../module'
|
|
6
5
|
|
|
7
|
-
export const dynamicViewRulePredicate = (
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return accept('warning', `Expression is not supported by dynamic views`, {
|
|
25
|
-
node: expr
|
|
26
|
-
})
|
|
27
|
-
default:
|
|
28
|
-
nonexhaustive(expr)
|
|
29
|
-
}
|
|
6
|
+
export const dynamicViewRulePredicate = (
|
|
7
|
+
_services: LikeC4Services
|
|
8
|
+
): ValidationCheck<ast.DynamicViewRulePredicateIterator> => {
|
|
9
|
+
return (expr, accept) => {
|
|
10
|
+
switch (true) {
|
|
11
|
+
case ast.isElementRef(expr.value):
|
|
12
|
+
case ast.isElementDescedantsExpression(expr.value):
|
|
13
|
+
case ast.isCustomElementExpression(expr.value):
|
|
14
|
+
case ast.isExpandElementExpression(expr.value):
|
|
15
|
+
return
|
|
16
|
+
case ast.isElementKindExpression(expr.value):
|
|
17
|
+
case ast.isElementTagExpression(expr.value):
|
|
18
|
+
case ast.isWildcardExpression(expr.value): {
|
|
19
|
+
accept('warning', `Predicate is ignored, as not supported in dynamic views`, {
|
|
20
|
+
node: expr
|
|
21
|
+
})
|
|
22
|
+
return
|
|
30
23
|
}
|
|
31
|
-
|
|
32
|
-
|
|
24
|
+
default:
|
|
25
|
+
nonexhaustive(expr.value)
|
|
33
26
|
}
|
|
34
27
|
}
|
|
35
28
|
}
|
|
@@ -6,6 +6,7 @@ const { getDocument } = AstUtils
|
|
|
6
6
|
|
|
7
7
|
export const elementChecks = (services: LikeC4Services): ValidationCheck<ast.Element> => {
|
|
8
8
|
const fqnIndex = services.likec4.FqnIndex
|
|
9
|
+
const locator = services.workspace.AstNodeLocator
|
|
9
10
|
return (el, accept) => {
|
|
10
11
|
const fqn = fqnIndex.getFqn(el)
|
|
11
12
|
if (!fqn) {
|
|
@@ -15,12 +16,15 @@ export const elementChecks = (services: LikeC4Services): ValidationCheck<ast.Ele
|
|
|
15
16
|
})
|
|
16
17
|
return
|
|
17
18
|
}
|
|
19
|
+
const doc = getDocument(el)
|
|
20
|
+
const docUri = doc.uri
|
|
21
|
+
const elPath = locator.getAstNodePath(el)
|
|
18
22
|
const withSameFqn = fqnIndex
|
|
19
23
|
.byFqn(fqn)
|
|
20
|
-
.filter(v => v.
|
|
24
|
+
.filter(v => v.documentUri !== docUri || v.path !== elPath)
|
|
21
25
|
.head()
|
|
22
26
|
if (withSameFqn) {
|
|
23
|
-
const isAnotherDoc = withSameFqn.
|
|
27
|
+
const isAnotherDoc = withSameFqn.documentUri !== docUri
|
|
24
28
|
accept(
|
|
25
29
|
'error',
|
|
26
30
|
`Duplicate element name ${el.name !== fqn ? el.name + ' (' + fqn + ')' : el.name}`,
|
|
@@ -31,8 +35,8 @@ export const elementChecks = (services: LikeC4Services): ValidationCheck<ast.Ele
|
|
|
31
35
|
relatedInformation: [
|
|
32
36
|
{
|
|
33
37
|
location: {
|
|
34
|
-
range: withSameFqn.
|
|
35
|
-
uri: withSameFqn.
|
|
38
|
+
range: (withSameFqn.nameSegment?.range ?? withSameFqn.selectionSegment?.range)!,
|
|
39
|
+
uri: withSameFqn.documentUri.toString()
|
|
36
40
|
},
|
|
37
41
|
message: `conflicting element`
|
|
38
42
|
}
|
package/src/validation/index.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { LikeC4Services } from '../module'
|
|
|
4
4
|
import { dynamicViewRulePredicate } from './dynamic-view-rule'
|
|
5
5
|
import { dynamicViewStep } from './dynamic-view-step'
|
|
6
6
|
import { elementChecks } from './element'
|
|
7
|
-
import { opacityPropertyRuleChecks } from './property-checks'
|
|
7
|
+
import { iconPropertyRuleChecks, opacityPropertyRuleChecks } from './property-checks'
|
|
8
8
|
import { relationChecks } from './relation'
|
|
9
9
|
import {
|
|
10
10
|
elementKindChecks,
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
import { viewChecks } from './view'
|
|
18
18
|
import {
|
|
19
19
|
customElementExprChecks,
|
|
20
|
+
customRelationExprChecks,
|
|
20
21
|
expandElementExprChecks,
|
|
21
22
|
incomingExpressionChecks,
|
|
22
23
|
outgoingExpressionChecks
|
|
@@ -27,6 +28,7 @@ export function registerValidationChecks(services: LikeC4Services) {
|
|
|
27
28
|
const registry = services.validation.ValidationRegistry
|
|
28
29
|
registry.register<ast.LikeC4AstType>({
|
|
29
30
|
OpacityProperty: opacityPropertyRuleChecks(services),
|
|
31
|
+
IconProperty: iconPropertyRuleChecks(services),
|
|
30
32
|
SpecificationRule: specificationRuleChecks(services),
|
|
31
33
|
Model: modelRuleChecks(services),
|
|
32
34
|
ModelViews: modelViewsChecks(services),
|
|
@@ -36,12 +38,13 @@ export function registerValidationChecks(services: LikeC4Services) {
|
|
|
36
38
|
ElementKind: elementKindChecks(services),
|
|
37
39
|
Relation: relationChecks(services),
|
|
38
40
|
Tag: tagChecks(services),
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
DynamicViewRulePredicateIterator: dynamicViewRulePredicate(services),
|
|
42
|
+
CustomElementExpression: customElementExprChecks(services),
|
|
43
|
+
CustomRelationExpression: customRelationExprChecks(services),
|
|
44
|
+
ExpandElementExpression: expandElementExprChecks(services),
|
|
42
45
|
RelationshipKind: relationshipChecks(services),
|
|
43
|
-
|
|
44
|
-
|
|
46
|
+
IncomingRelationExpression: incomingExpressionChecks(services),
|
|
47
|
+
OutgoingRelationExpression: outgoingExpressionChecks(services)
|
|
45
48
|
})
|
|
46
49
|
const connection = services.shared.lsp.Connection
|
|
47
50
|
if (connection) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ValidationCheck } from 'langium'
|
|
2
|
-
import
|
|
2
|
+
import { ast } from '../ast'
|
|
3
3
|
import type { LikeC4Services } from '../module'
|
|
4
4
|
|
|
5
5
|
export const opacityPropertyRuleChecks = (
|
|
@@ -15,3 +15,25 @@ export const opacityPropertyRuleChecks = (
|
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
|
+
|
|
19
|
+
export const iconPropertyRuleChecks = (
|
|
20
|
+
_: LikeC4Services
|
|
21
|
+
): ValidationCheck<ast.IconProperty> => {
|
|
22
|
+
return (node, accept) => {
|
|
23
|
+
const container = node.$container
|
|
24
|
+
const anotherIcon = container.props.some(p => ast.isIconProperty(p) && p !== node)
|
|
25
|
+
if (anotherIcon) {
|
|
26
|
+
accept('error', `Icon must be defined once`, {
|
|
27
|
+
node
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
if (
|
|
31
|
+
ast.isStyleProperties(container) && ast.isElementBody(container.$container)
|
|
32
|
+
&& container.$container.props.some(p => ast.isIconProperty(p))
|
|
33
|
+
) {
|
|
34
|
+
accept('warning', `Redundant as icon defined on element`, {
|
|
35
|
+
node
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -1,21 +1,34 @@
|
|
|
1
|
+
import { nonexhaustive } from '@likec4/core'
|
|
1
2
|
import type { ValidationCheck } from 'langium'
|
|
3
|
+
import { AstUtils } from 'langium'
|
|
2
4
|
import { ast } from '../../ast'
|
|
3
5
|
import type { LikeC4Services } from '../../module'
|
|
4
6
|
|
|
5
7
|
export const customElementExprChecks = (
|
|
6
8
|
_services: LikeC4Services
|
|
7
|
-
): ValidationCheck<ast.
|
|
9
|
+
): ValidationCheck<ast.CustomElementExpression> => {
|
|
8
10
|
return (el, accept) => {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
const container = AstUtils.getContainerOfType(el, ast.isViewRulePredicate)
|
|
12
|
+
if (ast.isExcludePredicate(container)) {
|
|
13
|
+
accept('error', 'Invalid usage inside "exclude"', {
|
|
11
14
|
node: el
|
|
12
15
|
})
|
|
13
16
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
switch (true) {
|
|
18
|
+
case ast.isElementRef(el.target):
|
|
19
|
+
case ast.isElementDescedantsExpression(el.target):
|
|
20
|
+
case ast.isExpandElementExpression(el.target):
|
|
21
|
+
return
|
|
22
|
+
case ast.isElementKindExpression(el.target):
|
|
23
|
+
case ast.isElementTagExpression(el.target):
|
|
24
|
+
case ast.isWildcardExpression(el.target):
|
|
25
|
+
accept('error', 'Invalid target (expect reference to specific element)', {
|
|
26
|
+
node: el,
|
|
27
|
+
property: 'target'
|
|
28
|
+
})
|
|
29
|
+
return
|
|
30
|
+
default:
|
|
31
|
+
nonexhaustive(el.target)
|
|
19
32
|
}
|
|
20
33
|
}
|
|
21
34
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { AstUtils, type ValidationCheck } from 'langium'
|
|
2
|
+
import { ast } from '../../ast'
|
|
3
|
+
import type { LikeC4Services } from '../../module'
|
|
4
|
+
|
|
5
|
+
export const customRelationExprChecks = (
|
|
6
|
+
_services: LikeC4Services
|
|
7
|
+
): ValidationCheck<ast.CustomRelationExpression> => {
|
|
8
|
+
return (el, accept) => {
|
|
9
|
+
const container = AstUtils.getContainerOfType(el, ast.isViewRulePredicate)
|
|
10
|
+
if (ast.isExcludePredicate(container)) {
|
|
11
|
+
accept('error', 'Invalid usage inside "exclude"', {
|
|
12
|
+
node: el
|
|
13
|
+
})
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -1,34 +1,23 @@
|
|
|
1
1
|
import { nonexhaustive } from '@likec4/core'
|
|
2
|
-
import type
|
|
2
|
+
import { type AstNode, AstUtils, type ValidationCheck } from 'langium'
|
|
3
3
|
import { ast } from '../../ast'
|
|
4
4
|
import type { LikeC4Services } from '../../module'
|
|
5
5
|
|
|
6
6
|
export const expandElementExprChecks = (
|
|
7
7
|
_services: LikeC4Services
|
|
8
|
-
): ValidationCheck<ast.
|
|
8
|
+
): ValidationCheck<ast.ExpandElementExpression> => {
|
|
9
9
|
return (el, accept) => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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)
|
|
10
|
+
const isInside = <T extends AstNode>(typePredicate: (n: AstNode) => n is T): boolean =>
|
|
11
|
+
!!AstUtils.getContainerOfType(el, typePredicate)
|
|
12
|
+
if (isInside(ast.isRelationExpression)) {
|
|
13
|
+
accept('warning', `Redundant usage, expand predicate resolves parent element only when used in relations`, {
|
|
14
|
+
node: el
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
if (isInside(ast.isExcludePredicate)) {
|
|
18
|
+
accept('warning', `Expand predicate is ignored in exclude`, {
|
|
19
|
+
node: el
|
|
20
|
+
})
|
|
32
21
|
}
|
|
33
22
|
}
|
|
34
23
|
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { AstUtils, type ValidationCheck } from 'langium'
|
|
2
2
|
import { isNullish } from 'remeda'
|
|
3
3
|
import { ast } from '../../ast'
|
|
4
4
|
import type { LikeC4Services } from '../../module'
|
|
5
5
|
|
|
6
6
|
export const incomingExpressionChecks = (
|
|
7
7
|
_services: LikeC4Services
|
|
8
|
-
): ValidationCheck<ast.
|
|
8
|
+
): ValidationCheck<ast.IncomingRelationExpression> => {
|
|
9
9
|
return (el, accept) => {
|
|
10
|
-
if (ast.
|
|
11
|
-
const view = el
|
|
12
|
-
if (isNullish(view
|
|
10
|
+
if (ast.isWildcardExpression(el.to) && ast.isExpressions(el.$container)) {
|
|
11
|
+
const view = AstUtils.getContainerOfType(el, ast.isElementView)
|
|
12
|
+
if (isNullish(view?.viewOf)) {
|
|
13
13
|
accept('warning', 'Predicate is ignored as it concerns all relationships', {
|
|
14
14
|
node: el
|
|
15
15
|
})
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { AstUtils, type ValidationCheck } from 'langium'
|
|
2
2
|
import { isNullish } from 'remeda'
|
|
3
3
|
import { ast } from '../../ast'
|
|
4
4
|
import type { LikeC4Services } from '../../module'
|
|
5
5
|
|
|
6
6
|
export const outgoingExpressionChecks = (
|
|
7
7
|
_services: LikeC4Services
|
|
8
|
-
): ValidationCheck<ast.
|
|
8
|
+
): ValidationCheck<ast.OutgoingRelationExpression> => {
|
|
9
9
|
return (el, accept) => {
|
|
10
|
-
if (ast.
|
|
11
|
-
const view = el
|
|
12
|
-
if (
|
|
10
|
+
if (ast.isWildcardExpression(el.from) && ast.isExpressions(el.$container)) {
|
|
11
|
+
const view = AstUtils.getContainerOfType(el, ast.isElementView)
|
|
12
|
+
if (isNullish(view?.viewOf)) {
|
|
13
13
|
accept('warning', 'Predicate is ignored as it concerns all relationships', {
|
|
14
14
|
node: el
|
|
15
15
|
})
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { type ComputedView, type Fqn, isComputedElementView, type ViewID } from '@likec4/core'
|
|
2
|
-
import { find } from 'remeda'
|
|
2
|
+
import { find, isNullish } from 'remeda'
|
|
3
3
|
|
|
4
4
|
export function assignNavigateTo<R extends Iterable<ComputedView>>(views: R): R {
|
|
5
5
|
const allElementViews = new Map<Fqn, ViewID[]>()
|
|
6
6
|
|
|
7
7
|
for (const v of views) {
|
|
8
|
-
if (isComputedElementView(v) && v.viewOf &&
|
|
8
|
+
if (isComputedElementView(v) && v.viewOf && isNullish(v.extends)) {
|
|
9
9
|
const viewsOf = allElementViews.get(v.viewOf) ?? []
|
|
10
10
|
viewsOf.push(v.id)
|
|
11
11
|
allElementViews.set(v.viewOf, viewsOf)
|