@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.
- package/contrib/likec4.monarch.ts +4 -4
- package/contrib/likec4.tmLanguage.json +1 -1
- 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} +58 -43
- 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/src/validation/dynamic-view-rule.ts +35 -0
- 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} +25 -17
- 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/src/validation/view-predicates/expanded-element.ts +34 -0
- 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 -133
- package/dist/ast.js +0 -267
- 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 -559
- package/dist/generated/ast.js +0 -868
- 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 -37
- 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 -184
- 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 -352
- package/dist/model/model-locator.d.ts +0 -22
- package/dist/model/model-locator.js +0 -119
- package/dist/model/model-parser.d.ts +0 -27
- package/dist/model/model-parser.js +0 -410
- package/dist/model-change/ModelChanges.d.ts +0 -15
- package/dist/model-change/ModelChanges.js +0 -100
- package/dist/model-change/changeElementStyle.d.ts +0 -15
- package/dist/model-change/changeElementStyle.js +0 -141
- package/dist/model-change/changeViewLayout.d.ts +0 -13
- package/dist/model-change/changeViewLayout.js +0 -30
- 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 -108
- 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 -58
- 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/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/expanded-element.js +0 -28
- 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 -18
- package/dist/view-utils/assignNavigateTo.d.ts +0 -3
- package/dist/view-utils/assignNavigateTo.js +0 -23
- 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 -76
- /package/{dist → src}/reset.d.ts +0 -0
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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
|
|
13
|
-
import { viewChecks } from
|
|
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
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
registry.
|
|
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
|
-
|
|
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,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,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
|