@likec4/language-server 1.7.4 → 1.8.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.tmLanguage.json +1 -1
- package/package.json +9 -9
- package/src/ast.ts +34 -12
- package/src/generated/ast.ts +215 -120
- package/src/generated/grammar.ts +1 -1
- package/src/like-c4.langium +63 -23
- package/src/lsp/SemanticTokenProvider.ts +2 -2
- package/src/model/model-builder.ts +30 -13
- package/src/model/model-parser.ts +92 -22
- package/src/model-graph/compute-view/compute.ts +27 -5
- package/src/model-graph/dynamic-view/compute.ts +11 -3
- package/src/model-graph/utils/applyCustomElementProperties.ts +4 -3
- package/src/model-graph/utils/applyViewRuleStyles.ts +3 -0
- package/src/model-graph/utils/buildElementNotations.ts +63 -0
- package/src/references/scope-computation.ts +5 -5
- package/src/references/scope-provider.ts +21 -9
- package/src/validation/_shared.ts +24 -0
- package/src/validation/dynamic-view-rule.ts +0 -6
- package/src/validation/element.ts +9 -9
- package/src/validation/index.ts +2 -1
- package/src/validation/property-checks.ts +1 -1
- package/src/validation/relation.ts +45 -39
- package/src/validation/specification.ts +15 -2
- package/src/validation/view.ts +7 -0
- package/src/view-utils/view-hash.ts +12 -18
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ComputedNode, ViewRule } from '@likec4/core'
|
|
2
|
-
import { Expr
|
|
3
|
-
import { isEmpty,
|
|
2
|
+
import { Expr } from '@likec4/core'
|
|
3
|
+
import { isEmpty, isNullish, omitBy } from 'remeda'
|
|
4
4
|
import { elementExprToPredicate } from './elementExpressionToPredicate'
|
|
5
5
|
|
|
6
6
|
export function applyCustomElementProperties(_rules: ViewRule[], _nodes: ComputedNode[]) {
|
|
@@ -15,12 +15,13 @@ export function applyCustomElementProperties(_rules: ViewRule[], _nodes: Compute
|
|
|
15
15
|
} of rules
|
|
16
16
|
) {
|
|
17
17
|
const { border, opacity, ...rest } = omitBy(props, isNullish)
|
|
18
|
+
const notEmpty = !isEmpty(rest)
|
|
18
19
|
const satisfies = elementExprToPredicate(expr)
|
|
19
20
|
nodes.forEach((node, i) => {
|
|
20
21
|
if (!satisfies(node)) {
|
|
21
22
|
return
|
|
22
23
|
}
|
|
23
|
-
if (
|
|
24
|
+
if (notEmpty) {
|
|
24
25
|
node = {
|
|
25
26
|
...node,
|
|
26
27
|
isCustomized: true,
|
|
@@ -25,6 +25,9 @@ export function applyViewRuleStyles(_rules: ViewRule[], nodes: ComputedNode[]) {
|
|
|
25
25
|
if (isDefined(rule.style.icon)) {
|
|
26
26
|
n.icon = rule.style.icon
|
|
27
27
|
}
|
|
28
|
+
if (isDefined(rule.notation)) {
|
|
29
|
+
n.notation = rule.notation
|
|
30
|
+
}
|
|
28
31
|
let styleOverride: ComputedNode['style'] | undefined
|
|
29
32
|
if (isDefined(rule.style.border)) {
|
|
30
33
|
styleOverride = { border: rule.style.border }
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { ComputedNode, ElementNotation } from '@likec4/core'
|
|
2
|
+
import { entries, flatMap, groupBy, map, mapValues, pipe, piped, prop, sortBy, unique } from 'remeda'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Build element notations from computed nodes:
|
|
6
|
+
* 1. Group by notation
|
|
7
|
+
* 2. Group by shape
|
|
8
|
+
* 3. Group by color
|
|
9
|
+
* 4. For each group get unique kinds
|
|
10
|
+
* 5. Unwind the groups
|
|
11
|
+
*/
|
|
12
|
+
export function buildElementNotations(nodes: ComputedNode[]): ElementNotation[] {
|
|
13
|
+
return pipe(
|
|
14
|
+
nodes,
|
|
15
|
+
groupBy(prop('notation')),
|
|
16
|
+
mapValues(
|
|
17
|
+
piped(
|
|
18
|
+
groupBy(prop('shape')),
|
|
19
|
+
mapValues(
|
|
20
|
+
piped(
|
|
21
|
+
groupBy(prop('color')),
|
|
22
|
+
mapValues(
|
|
23
|
+
piped(
|
|
24
|
+
map(prop('kind')),
|
|
25
|
+
unique()
|
|
26
|
+
)
|
|
27
|
+
),
|
|
28
|
+
entries(),
|
|
29
|
+
map(([color, kinds]) => ({
|
|
30
|
+
kinds,
|
|
31
|
+
color
|
|
32
|
+
}))
|
|
33
|
+
)
|
|
34
|
+
),
|
|
35
|
+
entries(),
|
|
36
|
+
flatMap(([shape, colors]) =>
|
|
37
|
+
colors.map(({ color, kinds }) => ({
|
|
38
|
+
shape,
|
|
39
|
+
color,
|
|
40
|
+
kinds
|
|
41
|
+
}))
|
|
42
|
+
)
|
|
43
|
+
)
|
|
44
|
+
),
|
|
45
|
+
entries(),
|
|
46
|
+
flatMap(([title, shapes]) =>
|
|
47
|
+
shapes.map(({ shape, color, kinds }) => ({
|
|
48
|
+
title,
|
|
49
|
+
shape,
|
|
50
|
+
color,
|
|
51
|
+
kinds
|
|
52
|
+
}))
|
|
53
|
+
),
|
|
54
|
+
sortBy(
|
|
55
|
+
prop('title'),
|
|
56
|
+
prop('shape'),
|
|
57
|
+
[
|
|
58
|
+
n => n.kinds.length,
|
|
59
|
+
'desc'
|
|
60
|
+
]
|
|
61
|
+
)
|
|
62
|
+
)
|
|
63
|
+
}
|
|
@@ -82,15 +82,15 @@ export class LikeC4ScopeComputation extends DefaultScopeComputation {
|
|
|
82
82
|
docExports: AstNodeDescription[],
|
|
83
83
|
document: LikeC4LangiumDocument
|
|
84
84
|
) {
|
|
85
|
-
if (isNullish(likec4lib)
|
|
85
|
+
if (isNullish(likec4lib)) {
|
|
86
86
|
return
|
|
87
87
|
}
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
try {
|
|
89
|
+
for (const iconAst of likec4lib.flatMap(l => l.icons)) {
|
|
90
90
|
docExports.push(this.descriptions.createDescription(iconAst, iconAst.name, document))
|
|
91
|
-
} catch (e) {
|
|
92
|
-
logError(e)
|
|
93
91
|
}
|
|
92
|
+
} catch (e) {
|
|
93
|
+
logError(e)
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { invariant, type likec4 as c4 } from '@likec4/core'
|
|
2
2
|
import type { AstNode } from 'langium'
|
|
3
3
|
import {
|
|
4
4
|
type AstNodeDescription,
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
DONE_RESULT,
|
|
8
8
|
EMPTY_SCOPE,
|
|
9
9
|
EMPTY_STREAM,
|
|
10
|
+
MapScope,
|
|
10
11
|
type ReferenceInfo,
|
|
11
12
|
type Scope,
|
|
12
13
|
type Stream,
|
|
@@ -102,6 +103,17 @@ export class LikeC4ScopeProvider extends DefaultScopeProvider {
|
|
|
102
103
|
if (parent) {
|
|
103
104
|
return new StreamScope(this.scopeElementRef(parent))
|
|
104
105
|
}
|
|
106
|
+
// if we have elementRef "this" or "it" we resolve it to the closest element
|
|
107
|
+
if (context.reference.$refText === 'this' || context.reference.$refText === 'it') {
|
|
108
|
+
const closestElement = AstUtils.getContainerOfType(container, ast.isElement)
|
|
109
|
+
if (closestElement) {
|
|
110
|
+
return new MapScope([
|
|
111
|
+
this.descriptions.createDescription(closestElement, context.reference.$refText)
|
|
112
|
+
])
|
|
113
|
+
} else {
|
|
114
|
+
return EMPTY_SCOPE
|
|
115
|
+
}
|
|
116
|
+
}
|
|
105
117
|
}
|
|
106
118
|
return this.computeScope(context)
|
|
107
119
|
} catch (e) {
|
|
@@ -116,26 +128,26 @@ export class LikeC4ScopeProvider extends DefaultScopeProvider {
|
|
|
116
128
|
|
|
117
129
|
protected computeScope(context: ReferenceInfo) {
|
|
118
130
|
const referenceType = this.reflection.getReferenceType(context)
|
|
131
|
+
// computeScope is called only for elements
|
|
132
|
+
invariant(referenceType === ast.Element, 'Invalid reference type')
|
|
119
133
|
const scopes: Stream<AstNodeDescription>[] = []
|
|
120
134
|
const doc = getDocument(context.container)
|
|
121
135
|
const precomputed = doc.precomputedScopes
|
|
122
136
|
|
|
123
137
|
if (precomputed) {
|
|
124
138
|
const byReferenceType = (desc: AstNodeDescription) => this.reflection.isSubtype(desc.type, referenceType)
|
|
125
|
-
|
|
126
139
|
let container: AstNode | undefined = context.container
|
|
127
140
|
while (container) {
|
|
128
141
|
const elements = precomputed.get(container).filter(byReferenceType)
|
|
129
142
|
if (elements.length > 0) {
|
|
130
143
|
scopes.push(stream(elements))
|
|
131
144
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
145
|
+
|
|
146
|
+
if (ast.isExtendElementBody(container)) {
|
|
147
|
+
scopes.push(this.scopeExtendElement(container.$container))
|
|
148
|
+
}
|
|
149
|
+
if (ast.isElementViewBody(container)) {
|
|
150
|
+
scopes.push(this.scopeElementView(container.$container))
|
|
139
151
|
}
|
|
140
152
|
container = container.$container
|
|
141
153
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type AstNode, interruptAndCheck, type ValidationAcceptor, type ValidationCheck } from 'langium'
|
|
2
|
+
import type { CancellationToken } from 'vscode-jsonrpc'
|
|
3
|
+
import { logWarnError } from '../logger'
|
|
4
|
+
|
|
5
|
+
export const RESERVED_WORDS = [
|
|
6
|
+
'this',
|
|
7
|
+
'it',
|
|
8
|
+
'self',
|
|
9
|
+
'super',
|
|
10
|
+
'likec4lib'
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
export function tryOrLog<T extends AstNode>(fn: ValidationCheck<T>): ValidationCheck<T> {
|
|
14
|
+
return async (node: T, accept: ValidationAcceptor, cancelToken: CancellationToken) => {
|
|
15
|
+
if (cancelToken) {
|
|
16
|
+
await interruptAndCheck(cancelToken)
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
await fn(node, accept, cancelToken)
|
|
20
|
+
} catch (e) {
|
|
21
|
+
logWarnError(e)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -9,10 +9,6 @@ export const dynamicViewRulePredicate = (
|
|
|
9
9
|
return (predicate, accept) => {
|
|
10
10
|
const expr = elementExpressionFromPredicate(predicate.value)
|
|
11
11
|
switch (true) {
|
|
12
|
-
case ast.isElementRef(expr):
|
|
13
|
-
case ast.isElementDescedantsExpression(expr):
|
|
14
|
-
case ast.isExpandElementExpression(expr):
|
|
15
|
-
return
|
|
16
12
|
case ast.isElementKindExpression(expr):
|
|
17
13
|
case ast.isElementTagExpression(expr):
|
|
18
14
|
case ast.isWildcardExpression(expr): {
|
|
@@ -21,8 +17,6 @@ export const dynamicViewRulePredicate = (
|
|
|
21
17
|
})
|
|
22
18
|
return
|
|
23
19
|
}
|
|
24
|
-
default:
|
|
25
|
-
nonexhaustive(expr)
|
|
26
20
|
}
|
|
27
21
|
}
|
|
28
22
|
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { AstUtils, type ValidationCheck } from 'langium'
|
|
2
2
|
import type { ast } from '../ast'
|
|
3
3
|
import type { LikeC4Services } from '../module'
|
|
4
|
+
import { RESERVED_WORDS, tryOrLog } from './_shared'
|
|
4
5
|
|
|
5
6
|
const { getDocument } = AstUtils
|
|
6
7
|
|
|
7
8
|
export const elementChecks = (services: LikeC4Services): ValidationCheck<ast.Element> => {
|
|
8
9
|
const fqnIndex = services.likec4.FqnIndex
|
|
9
10
|
const locator = services.workspace.AstNodeLocator
|
|
10
|
-
return (el, accept) => {
|
|
11
|
+
return tryOrLog((el, accept) => {
|
|
11
12
|
const fqn = fqnIndex.getFqn(el)
|
|
12
13
|
if (!fqn) {
|
|
13
14
|
accept('error', 'Not indexed element', {
|
|
@@ -16,6 +17,12 @@ export const elementChecks = (services: LikeC4Services): ValidationCheck<ast.Ele
|
|
|
16
17
|
})
|
|
17
18
|
return
|
|
18
19
|
}
|
|
20
|
+
if (RESERVED_WORDS.includes(el.name)) {
|
|
21
|
+
accept('error', `Reserved word: ${el.name}`, {
|
|
22
|
+
node: el,
|
|
23
|
+
property: 'name'
|
|
24
|
+
})
|
|
25
|
+
}
|
|
19
26
|
const doc = getDocument(el)
|
|
20
27
|
const docUri = doc.uri
|
|
21
28
|
const elPath = locator.getAstNodePath(el)
|
|
@@ -45,12 +52,5 @@ export const elementChecks = (services: LikeC4Services): ValidationCheck<ast.Ele
|
|
|
45
52
|
}
|
|
46
53
|
)
|
|
47
54
|
}
|
|
48
|
-
|
|
49
|
-
// accept('error', `Too many properties, max 3 allowed`, {
|
|
50
|
-
// node: el,
|
|
51
|
-
// property: 'props',
|
|
52
|
-
// index: i
|
|
53
|
-
// })
|
|
54
|
-
// }
|
|
55
|
-
}
|
|
55
|
+
})
|
|
56
56
|
}
|
package/src/validation/index.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { dynamicViewRulePredicate } from './dynamic-view-rule'
|
|
|
5
5
|
import { dynamicViewStep } from './dynamic-view-step'
|
|
6
6
|
import { elementChecks } from './element'
|
|
7
7
|
import { iconPropertyRuleChecks, opacityPropertyRuleChecks } from './property-checks'
|
|
8
|
-
import { relationChecks } from './relation'
|
|
8
|
+
import { relationBodyChecks, relationChecks } from './relation'
|
|
9
9
|
import {
|
|
10
10
|
elementKindChecks,
|
|
11
11
|
modelRuleChecks,
|
|
@@ -37,6 +37,7 @@ export function registerValidationChecks(services: LikeC4Services) {
|
|
|
37
37
|
Element: elementChecks(services),
|
|
38
38
|
ElementKind: elementKindChecks(services),
|
|
39
39
|
Relation: relationChecks(services),
|
|
40
|
+
RelationBody: relationBodyChecks(services),
|
|
40
41
|
Tag: tagChecks(services),
|
|
41
42
|
DynamicViewPredicateIterator: dynamicViewRulePredicate(services),
|
|
42
43
|
ElementPredicateWith: elementPredicateWithChecks(services),
|
|
@@ -28,7 +28,7 @@ export const iconPropertyRuleChecks = (
|
|
|
28
28
|
})
|
|
29
29
|
}
|
|
30
30
|
if (
|
|
31
|
-
ast.
|
|
31
|
+
ast.isElementStyleProperty(container) && ast.isElementBody(container.$container)
|
|
32
32
|
&& container.$container.props.some(p => ast.isIconProperty(p))
|
|
33
33
|
) {
|
|
34
34
|
accept('warning', `Redundant as icon defined on element`, {
|
|
@@ -1,57 +1,63 @@
|
|
|
1
1
|
import { isSameHierarchy } from '@likec4/core'
|
|
2
2
|
import type { ValidationCheck } from 'langium'
|
|
3
|
+
import { isDefined } from 'remeda'
|
|
3
4
|
import { ast } from '../ast'
|
|
4
5
|
import { elementRef } from '../elementRef'
|
|
5
|
-
import { logError } from '../logger'
|
|
6
6
|
import type { LikeC4Services } from '../module'
|
|
7
|
+
import { tryOrLog } from './_shared'
|
|
7
8
|
|
|
8
9
|
export const relationChecks = (services: LikeC4Services): ValidationCheck<ast.Relation> => {
|
|
9
10
|
const fqnIndex = services.likec4.FqnIndex
|
|
10
|
-
return (el, accept) => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
return tryOrLog((el, accept) => {
|
|
12
|
+
const targetEl: ast.Element | undefined = elementRef(el.target)
|
|
13
|
+
const target = targetEl && fqnIndex.getFqn(targetEl)
|
|
14
|
+
if (!target) {
|
|
15
|
+
accept('error', 'Target not resolved', {
|
|
16
|
+
node: el,
|
|
17
|
+
property: 'target'
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
let sourceEl
|
|
21
|
+
if (isDefined(el.source)) {
|
|
22
|
+
sourceEl = elementRef(el.source)
|
|
23
|
+
if (!sourceEl) {
|
|
24
|
+
return accept('error', 'Source not resolved', {
|
|
16
25
|
node: el,
|
|
17
|
-
property: '
|
|
26
|
+
property: 'source'
|
|
18
27
|
})
|
|
19
28
|
}
|
|
20
|
-
|
|
21
|
-
if (ast.
|
|
22
|
-
|
|
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)', {
|
|
29
|
+
} else {
|
|
30
|
+
if (!ast.isElementBody(el.$container)) {
|
|
31
|
+
return accept('error', 'Sourceless relation must be nested', {
|
|
37
32
|
node: el
|
|
38
33
|
})
|
|
39
34
|
}
|
|
35
|
+
sourceEl = el.$container.$container
|
|
36
|
+
}
|
|
40
37
|
|
|
41
|
-
|
|
42
|
-
accept('error', 'Invalid parent-child relationship', {
|
|
43
|
-
node: el
|
|
44
|
-
})
|
|
45
|
-
}
|
|
38
|
+
const source = fqnIndex.getFqn(sourceEl)
|
|
46
39
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
40
|
+
if (!source) {
|
|
41
|
+
accept('error', 'Source not resolved', {
|
|
42
|
+
node: el
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (source && target && isSameHierarchy(source, target)) {
|
|
47
|
+
accept('error', 'Invalid parent-child relationship', {
|
|
48
|
+
node: el
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const relationBodyChecks = (_services: LikeC4Services): ValidationCheck<ast.RelationBody> => {
|
|
55
|
+
return tryOrLog((body, accept) => {
|
|
56
|
+
const relation = body.$container
|
|
57
|
+
if (relation.tags?.values && body.tags?.values) {
|
|
58
|
+
accept('error', 'Relation cannot have tags in both header and body', {
|
|
59
|
+
node: body.tags
|
|
60
|
+
})
|
|
55
61
|
}
|
|
56
|
-
}
|
|
62
|
+
})
|
|
57
63
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AstUtils, type ValidationCheck } from 'langium'
|
|
2
2
|
import { ast } from '../ast'
|
|
3
3
|
import type { LikeC4Services } from '../module'
|
|
4
|
+
import { RESERVED_WORDS, tryOrLog } from './_shared'
|
|
4
5
|
|
|
5
6
|
export const specificationRuleChecks = (
|
|
6
7
|
_: LikeC4Services
|
|
@@ -39,7 +40,13 @@ export const modelViewsChecks = (_: LikeC4Services): ValidationCheck<ast.ModelVi
|
|
|
39
40
|
|
|
40
41
|
export const elementKindChecks = (services: LikeC4Services): ValidationCheck<ast.ElementKind> => {
|
|
41
42
|
const index = services.shared.workspace.IndexManager
|
|
42
|
-
return (node, accept) => {
|
|
43
|
+
return tryOrLog((node, accept) => {
|
|
44
|
+
if (RESERVED_WORDS.includes(node.name)) {
|
|
45
|
+
accept('error', `Reserved word: ${node.name}`, {
|
|
46
|
+
node: node,
|
|
47
|
+
property: 'name'
|
|
48
|
+
})
|
|
49
|
+
}
|
|
43
50
|
const sameKind = index
|
|
44
51
|
.allElements(ast.ElementKind)
|
|
45
52
|
.filter(n => n.name === node.name && n.node !== node)
|
|
@@ -62,7 +69,7 @@ export const elementKindChecks = (services: LikeC4Services): ValidationCheck<ast
|
|
|
62
69
|
}
|
|
63
70
|
})
|
|
64
71
|
}
|
|
65
|
-
}
|
|
72
|
+
})
|
|
66
73
|
}
|
|
67
74
|
|
|
68
75
|
export const tagChecks = (services: LikeC4Services): ValidationCheck<ast.Tag> => {
|
|
@@ -103,6 +110,12 @@ export const relationshipChecks = (
|
|
|
103
110
|
): ValidationCheck<ast.RelationshipKind> => {
|
|
104
111
|
const index = services.shared.workspace.IndexManager
|
|
105
112
|
return (node, accept) => {
|
|
113
|
+
if (RESERVED_WORDS.includes(node.name)) {
|
|
114
|
+
accept('error', `Reserved word: ${node.name}`, {
|
|
115
|
+
node: node,
|
|
116
|
+
property: 'name'
|
|
117
|
+
})
|
|
118
|
+
}
|
|
106
119
|
const sameKinds = index
|
|
107
120
|
.allElements(ast.RelationshipKind)
|
|
108
121
|
.filter(n => n.name === node.name)
|
package/src/validation/view.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type ValidationCheck } from 'langium'
|
|
2
2
|
import { ast } from '../ast'
|
|
3
3
|
import type { LikeC4Services } from '../module'
|
|
4
|
+
import { RESERVED_WORDS } from './_shared'
|
|
4
5
|
|
|
5
6
|
export const viewChecks = (services: LikeC4Services): ValidationCheck<ast.LikeC4View> => {
|
|
6
7
|
const index = services.shared.workspace.IndexManager
|
|
@@ -15,6 +16,12 @@ export const viewChecks = (services: LikeC4Services): ValidationCheck<ast.LikeC4
|
|
|
15
16
|
if (!el.name) {
|
|
16
17
|
return
|
|
17
18
|
}
|
|
19
|
+
if (RESERVED_WORDS.includes(el.name)) {
|
|
20
|
+
accept('error', `Reserved word: ${el.name}`, {
|
|
21
|
+
node: el,
|
|
22
|
+
property: 'name'
|
|
23
|
+
})
|
|
24
|
+
}
|
|
18
25
|
const anotherViews = index
|
|
19
26
|
.allElements(ast.LikeC4View)
|
|
20
27
|
.filter(n => n.name === el.name)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ComputedView } from '@likec4/core/types'
|
|
2
2
|
import objectHash from 'object-hash'
|
|
3
|
-
import {
|
|
3
|
+
import { isTruthy, map, mapToObj, pick, pipe } from 'remeda'
|
|
4
4
|
import type { SetOptional } from 'type-fest'
|
|
5
5
|
|
|
6
6
|
export function calcViewLayoutHash<V extends ComputedView>(view: SetOptional<V, 'hash'>): V {
|
|
@@ -8,26 +8,20 @@ export function calcViewLayoutHash<V extends ComputedView>(view: SetOptional<V,
|
|
|
8
8
|
id: view.id,
|
|
9
9
|
__: view.__ ?? 'element',
|
|
10
10
|
autoLayout: view.autoLayout,
|
|
11
|
-
nodes:
|
|
12
|
-
.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
nodes: pipe(
|
|
12
|
+
view.nodes,
|
|
13
|
+
map(pick(['id', 'title', 'description', 'technology', 'shape', 'icon', 'children'])),
|
|
14
|
+
mapToObj(({ id, icon, ...node }) => [id, { ...node, icon: isTruthy(icon) ? 'Y' : 'N' }])
|
|
15
|
+
),
|
|
16
|
+
edges: pipe(
|
|
17
|
+
view.edges,
|
|
18
|
+
map(pick(['source', 'target', 'label', 'description', 'technology', 'dir', 'head', 'tail', 'line'])),
|
|
19
|
+
mapToObj(({ source, target, ...edge }) => [`${source}:${target}`, edge])
|
|
20
|
+
)
|
|
17
21
|
}
|
|
18
22
|
view.hash = objectHash(tohash, {
|
|
19
23
|
ignoreUnknown: true,
|
|
20
|
-
respectType: false
|
|
21
|
-
replacer(value) {
|
|
22
|
-
if (!isString(value)) {
|
|
23
|
-
return value
|
|
24
|
-
}
|
|
25
|
-
value = value.trim()
|
|
26
|
-
if (value.match('^(aws|tech|gcp|https|http)')) {
|
|
27
|
-
value = 'U' // hide urls
|
|
28
|
-
}
|
|
29
|
-
return value
|
|
30
|
-
}
|
|
24
|
+
respectType: false
|
|
31
25
|
})
|
|
32
26
|
return view as V
|
|
33
27
|
}
|