@likec4/language-server 1.20.2 → 1.21.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/dist/ast.d.ts +16 -0
- package/dist/ast.js +42 -10
- package/dist/bundled.mjs +4230 -4096
- package/dist/formatting/LikeC4Formatter.js +6 -3
- package/dist/generated/ast.d.ts +76 -24
- package/dist/generated/ast.js +103 -25
- package/dist/generated/grammar.js +1 -1
- package/dist/logger.d.ts +3 -0
- package/dist/logger.js +0 -4
- package/dist/lsp/CompletionProvider.js +2 -1
- package/dist/lsp/SemanticTokenProvider.js +50 -2
- package/dist/model/model-builder.js +57 -3
- package/dist/model/model-parser.d.ts +6 -0
- package/dist/model/model-parser.js +1 -0
- package/dist/model/parser/Base.js +11 -5
- package/dist/model/parser/DeploymentModelParser.d.ts +1 -0
- package/dist/model/parser/DeploymentViewParser.d.ts +1 -0
- package/dist/model/parser/DeploymentViewParser.js +3 -0
- package/dist/model/parser/FqnRefParser.d.ts +1 -0
- package/dist/model/parser/FqnRefParser.js +10 -0
- package/dist/model/parser/GlobalsParser.d.ts +1 -0
- package/dist/model/parser/ModelParser.d.ts +2 -1
- package/dist/model/parser/ModelParser.js +26 -1
- package/dist/model/parser/PredicatesParser.js +19 -1
- package/dist/model/parser/ViewsParser.d.ts +1 -0
- package/dist/references/scope-provider.d.ts +1 -1
- package/dist/references/scope-provider.js +1 -1
- package/dist/test/testServices.d.ts +1 -0
- package/dist/test/testServices.js +8 -0
- package/dist/utils/elementRef.d.ts +3 -3
- package/dist/validation/index.d.ts +1 -1
- package/package.json +12 -12
- package/src/ast.ts +55 -9
- package/src/formatting/LikeC4Formatter.ts +7 -1
- package/src/generated/ast.ts +200 -49
- package/src/generated/grammar.ts +1 -1
- package/src/like-c4.langium +45 -7
- package/src/logger.ts +3 -4
- package/src/lsp/CompletionProvider.ts +1 -0
- package/src/lsp/SemanticTokenProvider.ts +56 -1
- package/src/model/model-builder.ts +65 -6
- package/src/model/model-parser.ts +1 -0
- package/src/model/parser/Base.ts +11 -5
- package/src/model/parser/DeploymentViewParser.ts +3 -0
- package/src/model/parser/FqnRefParser.ts +11 -0
- package/src/model/parser/ModelParser.ts +30 -1
- package/src/model/parser/PredicatesParser.ts +19 -1
- package/src/references/scope-provider.ts +9 -9
- package/src/test/testServices.ts +18 -9
- package/src/utils/elementRef.ts +3 -3
package/src/like-c4.langium
CHANGED
|
@@ -125,10 +125,13 @@ ElementStringProperty:
|
|
|
125
125
|
key=('title' | 'technology' | 'description') ':'? value=String ';'?;
|
|
126
126
|
|
|
127
127
|
ExtendElement:
|
|
128
|
-
'extend' element=
|
|
128
|
+
'extend' element=StrictFqnElementRef body=ExtendElementBody
|
|
129
129
|
;
|
|
130
130
|
|
|
131
131
|
ExtendElementBody: '{'
|
|
132
|
+
tags=Tags?
|
|
133
|
+
props+=ExtendElementProperty*
|
|
134
|
+
|
|
132
135
|
elements+=(
|
|
133
136
|
Relation<true> |
|
|
134
137
|
Element
|
|
@@ -136,9 +139,13 @@ ExtendElementBody: '{'
|
|
|
136
139
|
'}'
|
|
137
140
|
;
|
|
138
141
|
|
|
142
|
+
ExtendElementProperty:
|
|
143
|
+
LinkProperty |
|
|
144
|
+
MetadataProperty;
|
|
145
|
+
|
|
139
146
|
//
|
|
140
|
-
|
|
141
|
-
el=[Element:Id] ({infer
|
|
147
|
+
StrictFqnElementRef:
|
|
148
|
+
el=[Element:Id] ({infer StrictFqnElementRef.parent=current} StickyDot el=[Element:Id])*;
|
|
142
149
|
|
|
143
150
|
ElementRef:
|
|
144
151
|
el=[Element:Id] ({infer ElementRef.parent=current} StickyDot el=[Element:Id])*;
|
|
@@ -362,7 +369,7 @@ WhereElementNegation:
|
|
|
362
369
|
|
|
363
370
|
WhereElement:
|
|
364
371
|
{infer WhereElementTag} 'tag' EqOperator value=[Tag:TagId]? |
|
|
365
|
-
{infer WhereElementKind} 'kind' EqOperator value=[
|
|
372
|
+
{infer WhereElementKind} 'kind' EqOperator value=[DeploymentNodeOrElementKind]?
|
|
366
373
|
;
|
|
367
374
|
|
|
368
375
|
ElementExpression:
|
|
@@ -627,7 +634,11 @@ DeploymentViewRulePredicateExpression:
|
|
|
627
634
|
|
|
628
635
|
ExpressionV2:
|
|
629
636
|
RelationPredicateOrWhereV2 |
|
|
630
|
-
|
|
637
|
+
ElementPredicateOrWhereV2
|
|
638
|
+
;
|
|
639
|
+
|
|
640
|
+
ElementPredicateOrWhereV2:
|
|
641
|
+
FqnExpr ({infer ElementPredicateWhereV2.subject=current} 'where' where=WhereElementExpression?)?
|
|
631
642
|
;
|
|
632
643
|
|
|
633
644
|
RelationPredicateOrWhereV2:
|
|
@@ -731,13 +742,40 @@ BorderStyleValue returns string:
|
|
|
731
742
|
BorderProperty:
|
|
732
743
|
key='border' ':'? value=BorderStyleValue ';'?;
|
|
733
744
|
|
|
745
|
+
SizeValue returns string:
|
|
746
|
+
'xs' |
|
|
747
|
+
'sm' |
|
|
748
|
+
'md' |
|
|
749
|
+
'lg' |
|
|
750
|
+
'xl' |
|
|
751
|
+
'xsmall' |
|
|
752
|
+
'small' |
|
|
753
|
+
'medium' |
|
|
754
|
+
'large' |
|
|
755
|
+
'xlarge';
|
|
756
|
+
|
|
757
|
+
ShapeSizeProperty:
|
|
758
|
+
key='size' ':'? value=SizeValue ';'?;
|
|
759
|
+
|
|
760
|
+
PaddingSizeProperty:
|
|
761
|
+
key='padding' ':'? value=SizeValue ';'?;
|
|
762
|
+
|
|
763
|
+
TextSizeProperty:
|
|
764
|
+
key='textSize' ':'? value=SizeValue ';'?;
|
|
765
|
+
|
|
766
|
+
type SizeProperty = ShapeSizeProperty | PaddingSizeProperty | TextSizeProperty;
|
|
767
|
+
|
|
734
768
|
StyleProperty:
|
|
735
769
|
ColorProperty |
|
|
736
770
|
ShapeProperty |
|
|
737
771
|
BorderProperty |
|
|
738
772
|
OpacityProperty |
|
|
739
773
|
IconProperty |
|
|
740
|
-
MultipleProperty
|
|
774
|
+
MultipleProperty |
|
|
775
|
+
ShapeSizeProperty |
|
|
776
|
+
PaddingSizeProperty |
|
|
777
|
+
TextSizeProperty
|
|
778
|
+
;
|
|
741
779
|
|
|
742
780
|
ElementStyleProperty:
|
|
743
781
|
key='style' '{'
|
|
@@ -802,7 +840,7 @@ CustomColorId returns string:
|
|
|
802
840
|
IdTerminal | ElementShape | ArrowType | LineOptions | 'element' | 'model';
|
|
803
841
|
|
|
804
842
|
Id returns string:
|
|
805
|
-
IdTerminal | ElementShape | ThemeColor | ArrowType | LineOptions | Participant | 'element' | 'model' | 'group' | 'node' | 'deployment' | 'instance';
|
|
843
|
+
IdTerminal | ElementShape | ThemeColor | ArrowType | LineOptions | Participant | SizeValue | 'element' | 'model' | 'group' | 'node' | 'deployment' | 'instance';
|
|
806
844
|
|
|
807
845
|
fragment EqOperator:
|
|
808
846
|
(
|
package/src/logger.ts
CHANGED
|
@@ -10,11 +10,10 @@ export function logError(err: unknown): void {
|
|
|
10
10
|
logger.error(err)
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Logs an error as warning (not critical)
|
|
15
|
+
*/
|
|
13
16
|
export function logWarnError(err: unknown): void {
|
|
14
|
-
if (err instanceof Error) {
|
|
15
|
-
logger.warn(err.stack ?? err.message)
|
|
16
|
-
return
|
|
17
|
-
}
|
|
18
17
|
logger.warn(err)
|
|
19
18
|
}
|
|
20
19
|
|
|
@@ -140,6 +140,60 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
|
|
|
140
140
|
})
|
|
141
141
|
return 'prune'
|
|
142
142
|
}
|
|
143
|
+
if (
|
|
144
|
+
ast.isGlobalStyleGroup(node)
|
|
145
|
+
|| ast.isGlobalStyle(node)
|
|
146
|
+
) {
|
|
147
|
+
acceptor({
|
|
148
|
+
node,
|
|
149
|
+
property: 'id',
|
|
150
|
+
type: SemanticTokenTypes.variable,
|
|
151
|
+
modifier: [
|
|
152
|
+
SemanticTokenModifiers.definition,
|
|
153
|
+
SemanticTokenModifiers.readonly,
|
|
154
|
+
],
|
|
155
|
+
})
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
if (ast.isViewRuleGlobalStyle(node)) {
|
|
159
|
+
acceptor({
|
|
160
|
+
node,
|
|
161
|
+
property: 'style',
|
|
162
|
+
type: SemanticTokenTypes.variable,
|
|
163
|
+
modifier: [
|
|
164
|
+
SemanticTokenModifiers.definition,
|
|
165
|
+
SemanticTokenModifiers.readonly,
|
|
166
|
+
],
|
|
167
|
+
})
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
if (
|
|
171
|
+
ast.isGlobalPredicateGroup(node)
|
|
172
|
+
|| ast.isGlobalDynamicPredicateGroup(node)
|
|
173
|
+
) {
|
|
174
|
+
acceptor({
|
|
175
|
+
node,
|
|
176
|
+
property: 'name',
|
|
177
|
+
type: SemanticTokenTypes.variable,
|
|
178
|
+
modifier: [
|
|
179
|
+
SemanticTokenModifiers.definition,
|
|
180
|
+
SemanticTokenModifiers.readonly,
|
|
181
|
+
],
|
|
182
|
+
})
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
if (ast.isViewRuleGlobalPredicateRef(node)) {
|
|
186
|
+
acceptor({
|
|
187
|
+
node,
|
|
188
|
+
property: 'predicate',
|
|
189
|
+
type: SemanticTokenTypes.variable,
|
|
190
|
+
modifier: [
|
|
191
|
+
SemanticTokenModifiers.definition,
|
|
192
|
+
SemanticTokenModifiers.readonly,
|
|
193
|
+
],
|
|
194
|
+
})
|
|
195
|
+
return
|
|
196
|
+
}
|
|
143
197
|
if (ast.isElementTagExpression(node) && isTruthy(node.tag)) {
|
|
144
198
|
acceptor({
|
|
145
199
|
node,
|
|
@@ -161,7 +215,7 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
|
|
|
161
215
|
})
|
|
162
216
|
return !node.parent ? 'prune' : undefined
|
|
163
217
|
}
|
|
164
|
-
if (ast.isElementRef(node) || ast.
|
|
218
|
+
if (ast.isElementRef(node) || ast.isStrictFqnElementRef(node)) {
|
|
165
219
|
acceptor({
|
|
166
220
|
node,
|
|
167
221
|
property: 'el',
|
|
@@ -276,6 +330,7 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
|
|
|
276
330
|
|| ast.isArrowProperty(node)
|
|
277
331
|
|| ast.isLineProperty(node)
|
|
278
332
|
|| ast.isBorderProperty(node)
|
|
333
|
+
|| ast.isSizeProperty(node)
|
|
279
334
|
) {
|
|
280
335
|
acceptor({
|
|
281
336
|
node,
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
compareRelations,
|
|
6
6
|
computeColorValues,
|
|
7
7
|
DeploymentElement,
|
|
8
|
+
isNonEmptyArray,
|
|
8
9
|
isScopedElementView,
|
|
9
10
|
LikeC4Model,
|
|
10
11
|
parentFqn,
|
|
@@ -18,6 +19,7 @@ import {
|
|
|
18
19
|
filter,
|
|
19
20
|
flatMap,
|
|
20
21
|
groupBy,
|
|
22
|
+
hasAtLeast,
|
|
21
23
|
indexBy,
|
|
22
24
|
isBoolean,
|
|
23
25
|
isDefined,
|
|
@@ -35,11 +37,13 @@ import {
|
|
|
35
37
|
reduce,
|
|
36
38
|
reverse,
|
|
37
39
|
sort,
|
|
40
|
+
unique,
|
|
38
41
|
values,
|
|
39
42
|
} from 'remeda'
|
|
40
43
|
import type {
|
|
41
44
|
ParsedAstDeploymentRelation,
|
|
42
45
|
ParsedAstElement,
|
|
46
|
+
ParsedAstExtendElement,
|
|
43
47
|
ParsedAstRelation,
|
|
44
48
|
ParsedAstSpecification,
|
|
45
49
|
ParsedAstView,
|
|
@@ -117,7 +121,10 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
117
121
|
icon,
|
|
118
122
|
opacity,
|
|
119
123
|
border,
|
|
124
|
+
size,
|
|
120
125
|
multiple,
|
|
126
|
+
padding,
|
|
127
|
+
textSize,
|
|
121
128
|
},
|
|
122
129
|
id,
|
|
123
130
|
kind,
|
|
@@ -140,6 +147,9 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
140
147
|
border ??= __kind.style.border
|
|
141
148
|
technology ??= __kind.technology
|
|
142
149
|
multiple ??= __kind.style.multiple
|
|
150
|
+
size ??= __kind.style.size
|
|
151
|
+
padding ??= __kind.style.padding
|
|
152
|
+
textSize ??= __kind.style.textSize
|
|
143
153
|
return {
|
|
144
154
|
...(color && { color }),
|
|
145
155
|
...(shape && { shape }),
|
|
@@ -148,6 +158,9 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
148
158
|
...(__kind.notation && { notation: __kind.notation }),
|
|
149
159
|
style: {
|
|
150
160
|
...(border && { border }),
|
|
161
|
+
...(size && { size }),
|
|
162
|
+
...(padding && { padding }),
|
|
163
|
+
...(textSize && { textSize }),
|
|
151
164
|
...(isBoolean(multiple) && { multiple }),
|
|
152
165
|
...(isNumber(opacity) && { opacity }),
|
|
153
166
|
},
|
|
@@ -166,9 +179,55 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
166
179
|
}
|
|
167
180
|
}
|
|
168
181
|
|
|
182
|
+
const elementsExtendData = new Map<string, Pick<c4.Element, 'links' | 'tags' | 'metadata'>>()
|
|
183
|
+
function mergeAllC4ExtendElements(doc: ParsedLikeC4LangiumDocument) {
|
|
184
|
+
for (const el of doc.c4ExtendElements) {
|
|
185
|
+
let links = el.links ? resolveLinks(doc, el.links) : null
|
|
186
|
+
const existing = elementsExtendData.get(el.id)
|
|
187
|
+
if (existing) {
|
|
188
|
+
links = existing.links ? [...existing.links, ...(links ?? [])] : links
|
|
189
|
+
|
|
190
|
+
let tags: c4.Tag[] | null = [...(existing.tags ?? []), ...(el.tags ?? [])]
|
|
191
|
+
if (!hasAtLeast(tags, 1)) {
|
|
192
|
+
tags = null
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
elementsExtendData.set(el.id, {
|
|
196
|
+
tags,
|
|
197
|
+
links,
|
|
198
|
+
metadata: { ...existing.metadata, ...el.metadata },
|
|
199
|
+
})
|
|
200
|
+
} else {
|
|
201
|
+
elementsExtendData.set(el.id, {
|
|
202
|
+
tags: el.tags ?? null,
|
|
203
|
+
links,
|
|
204
|
+
metadata: { ...el.metadata },
|
|
205
|
+
})
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
function withExtendElementData(el: c4.Element): c4.Element {
|
|
210
|
+
const extendData = elementsExtendData.get(el.id)
|
|
211
|
+
if (extendData) {
|
|
212
|
+
const links = [...(el.links ?? []), ...(extendData.links ?? [])]
|
|
213
|
+
const tags = unique([...(el.tags ?? []), ...(extendData.tags ?? [])])
|
|
214
|
+
const metadata = { ...el.metadata, ...extendData.metadata }
|
|
215
|
+
return {
|
|
216
|
+
...el,
|
|
217
|
+
tags: hasAtLeast(tags, 1) ? tags : null,
|
|
218
|
+
links: hasAtLeast(links, 1) ? links : null,
|
|
219
|
+
...(!isEmpty(metadata) && { metadata }),
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return el
|
|
223
|
+
}
|
|
224
|
+
|
|
169
225
|
const elements = pipe(
|
|
170
226
|
docs,
|
|
171
|
-
flatMap(d =>
|
|
227
|
+
flatMap(d => {
|
|
228
|
+
mergeAllC4ExtendElements(d)
|
|
229
|
+
return map(d.c4Elements, toModelElement(d))
|
|
230
|
+
}),
|
|
172
231
|
filter(isTruthy),
|
|
173
232
|
// sort from root elements to nested, so that parent is always present
|
|
174
233
|
// Import to preserve the order from the source
|
|
@@ -180,7 +239,7 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
180
239
|
logWarnError(`No parent found for ${el.id}`)
|
|
181
240
|
return acc
|
|
182
241
|
}
|
|
183
|
-
acc[el.id] = el
|
|
242
|
+
acc[el.id] = withExtendElementData(el)
|
|
184
243
|
return acc
|
|
185
244
|
},
|
|
186
245
|
{} as c4.ParsedLikeC4Model['elements'],
|
|
@@ -219,7 +278,7 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
219
278
|
} satisfies c4.ModelRelation
|
|
220
279
|
}
|
|
221
280
|
return {
|
|
222
|
-
...links && { links },
|
|
281
|
+
...(links && { links }),
|
|
223
282
|
...model,
|
|
224
283
|
source,
|
|
225
284
|
target,
|
|
@@ -263,8 +322,8 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
263
322
|
} = parsed
|
|
264
323
|
return {
|
|
265
324
|
...parsed,
|
|
266
|
-
...notation && { notation },
|
|
267
|
-
...technology && { technology },
|
|
325
|
+
...(notation && { notation }),
|
|
326
|
+
...(technology && { technology }),
|
|
268
327
|
style: {
|
|
269
328
|
border: 'dashed',
|
|
270
329
|
opacity: 10,
|
|
@@ -333,7 +392,7 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
|
|
|
333
392
|
} satisfies c4.DeploymentRelation
|
|
334
393
|
}
|
|
335
394
|
return {
|
|
336
|
-
...links && { links },
|
|
395
|
+
...(links && { links }),
|
|
337
396
|
...model,
|
|
338
397
|
source,
|
|
339
398
|
target,
|
package/src/model/parser/Base.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type * as c4 from '@likec4/core'
|
|
2
2
|
import { invariant, isNonEmptyArray } from '@likec4/core'
|
|
3
3
|
import type { AstNode } from 'langium'
|
|
4
|
-
import { filter, flatMap, isNonNullish, isTruthy,
|
|
4
|
+
import { filter, flatMap, fromEntries, isEmpty, isNonNullish, isTruthy, map, pipe, unique } from 'remeda'
|
|
5
5
|
import stripIndent from 'strip-indent'
|
|
6
6
|
import { type ParsedLikeC4LangiumDocument, type ParsedLink, ast } from '../../ast'
|
|
7
7
|
import type { LikeC4Services } from '../../module'
|
|
@@ -49,10 +49,16 @@ export class BaseParser {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
getMetadata(metadataAstNode: ast.MetadataProperty | undefined): { [key: string]: string } | undefined {
|
|
52
|
-
if (!metadataAstNode || !this.isValid(metadataAstNode)) {
|
|
52
|
+
if (!metadataAstNode || !this.isValid(metadataAstNode) || isEmpty(metadataAstNode.props)) {
|
|
53
53
|
return undefined
|
|
54
54
|
}
|
|
55
|
-
|
|
55
|
+
const data = pipe(
|
|
56
|
+
metadataAstNode.props,
|
|
57
|
+
map(p => [p.key, removeIndent(p.value)] as [string, string]),
|
|
58
|
+
filter(([_, value]) => isTruthy(value)),
|
|
59
|
+
fromEntries(),
|
|
60
|
+
)
|
|
61
|
+
return isEmpty(data) ? undefined : data
|
|
56
62
|
}
|
|
57
63
|
|
|
58
64
|
convertTags<E extends { tags?: ast.Tags }>(withTags?: E) {
|
|
@@ -63,7 +69,7 @@ export class BaseParser {
|
|
|
63
69
|
if (!iter) {
|
|
64
70
|
return null
|
|
65
71
|
}
|
|
66
|
-
|
|
72
|
+
let tags = [] as c4.Tag[]
|
|
67
73
|
while (iter) {
|
|
68
74
|
try {
|
|
69
75
|
if (this.isValid(iter)) {
|
|
@@ -77,7 +83,7 @@ export class BaseParser {
|
|
|
77
83
|
}
|
|
78
84
|
iter = iter.prev
|
|
79
85
|
}
|
|
80
|
-
tags.reverse()
|
|
86
|
+
tags = unique(tags.reverse())
|
|
81
87
|
return isNonEmptyArray(tags) ? tags : null
|
|
82
88
|
}
|
|
83
89
|
|
|
@@ -84,6 +84,9 @@ export function DeploymentViewParser<TBase extends WithExpressionV2 & WithDeploy
|
|
|
84
84
|
case ast.isFqnExpr(expr):
|
|
85
85
|
exprs.unshift(this.parseFqnExpr(expr))
|
|
86
86
|
break
|
|
87
|
+
case ast.isElementPredicateWhereV2(expr):
|
|
88
|
+
exprs.unshift(this.parseElementWhereExpr(expr))
|
|
89
|
+
break
|
|
87
90
|
case ast.isRelationExpr(expr):
|
|
88
91
|
exprs.unshift(this.parseRelationExpr(expr))
|
|
89
92
|
break
|
|
@@ -74,6 +74,17 @@ export function ExpressionV2Parser<TBase extends Base>(B: TBase) {
|
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
parseElementWhereExpr(astNode: ast.ElementPredicateWhereV2): c4.RelationExpr {
|
|
78
|
+
return {
|
|
79
|
+
where: {
|
|
80
|
+
expr: this.parseFqnExpr(astNode.subject as ast.FqnExpr),
|
|
81
|
+
condition: astNode.where ? parseWhereClause(astNode.where) : {
|
|
82
|
+
kind: { neq: '--always-true--' },
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
77
88
|
parseFqnExpressions(astNode: ast.FqnExpressions): c4.FqnExpr[] {
|
|
78
89
|
const exprs = [] as c4.FqnExpr[]
|
|
79
90
|
let iter: ast.FqnExpressions['prev'] = astNode
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type * as c4 from '@likec4/core'
|
|
2
2
|
import { isNonEmptyArray, nonexhaustive, nonNullable } from '@likec4/core'
|
|
3
|
-
import { filter, first, isNonNullish, isTruthy, map, mapToObj, pipe } from 'remeda'
|
|
3
|
+
import { filter, first, isEmpty, isNonNullish, isTruthy, map, mapToObj, pipe } from 'remeda'
|
|
4
4
|
import {
|
|
5
5
|
type ParsedAstElement,
|
|
6
|
+
type ParsedAstExtendElement,
|
|
6
7
|
type ParsedAstRelation,
|
|
7
8
|
ast,
|
|
8
9
|
resolveRelationPoints,
|
|
@@ -32,6 +33,13 @@ export function ModelParser<TBase extends Base>(B: TBase) {
|
|
|
32
33
|
}
|
|
33
34
|
continue
|
|
34
35
|
}
|
|
36
|
+
if (ast.isExtendElement(el)) {
|
|
37
|
+
const parsed = this.parseExtendElement(el)
|
|
38
|
+
if (parsed) {
|
|
39
|
+
doc.c4ExtendElements.push(parsed)
|
|
40
|
+
}
|
|
41
|
+
continue
|
|
42
|
+
}
|
|
35
43
|
nonexhaustive(el)
|
|
36
44
|
} catch (e) {
|
|
37
45
|
logWarnError(e)
|
|
@@ -87,6 +95,27 @@ export function ModelParser<TBase extends Base>(B: TBase) {
|
|
|
87
95
|
}
|
|
88
96
|
}
|
|
89
97
|
|
|
98
|
+
parseExtendElement(astNode: ast.ExtendElement): ParsedAstExtendElement | null {
|
|
99
|
+
const isValid = this.isValid
|
|
100
|
+
const id = this.resolveFqn(astNode)
|
|
101
|
+
const tags = this.parseTags(astNode.body)
|
|
102
|
+
const metadata = this.getMetadata(astNode.body?.props.find(ast.isMetadataProperty))
|
|
103
|
+
const astPath = this.getAstNodePath(astNode)
|
|
104
|
+
const links = this.parseLinks(astNode.body) ?? []
|
|
105
|
+
|
|
106
|
+
if (!tags && isEmpty(metadata ?? {}) && isEmpty(links)) {
|
|
107
|
+
return null
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
id,
|
|
112
|
+
astPath,
|
|
113
|
+
...(metadata && { metadata }),
|
|
114
|
+
...(tags && { tags }),
|
|
115
|
+
...(links && isNonEmptyArray(links) && { links }),
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
90
119
|
parseRelation(astNode: ast.Relation): ParsedAstRelation {
|
|
91
120
|
const isValid = this.isValid
|
|
92
121
|
const coupling = resolveRelationPoints(astNode)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type * as c4 from '@likec4/core'
|
|
2
2
|
import { invariant, nonexhaustive } from '@likec4/core'
|
|
3
3
|
import { isBoolean, isDefined, isTruthy } from 'remeda'
|
|
4
|
-
import { ast, parseAstOpacityProperty, toColor } from '../../ast'
|
|
4
|
+
import { ast, parseAstOpacityProperty, parseAstSizeValue, toColor } from '../../ast'
|
|
5
5
|
import { logWarnError } from '../../logger'
|
|
6
6
|
import { elementRef } from '../../utils/elementRef'
|
|
7
7
|
import { parseWhereClause } from '../model-parser-where'
|
|
@@ -180,6 +180,24 @@ export function PredicatesParser<TBase extends Base>(B: TBase) {
|
|
|
180
180
|
}
|
|
181
181
|
return acc
|
|
182
182
|
}
|
|
183
|
+
if (ast.isShapeSizeProperty(prop)) {
|
|
184
|
+
if (isTruthy(prop.value)) {
|
|
185
|
+
acc.custom[prop.key] = parseAstSizeValue(prop)
|
|
186
|
+
}
|
|
187
|
+
return acc
|
|
188
|
+
}
|
|
189
|
+
if (ast.isTextSizeProperty(prop)) {
|
|
190
|
+
if (isTruthy(prop.value)) {
|
|
191
|
+
acc.custom[prop.key] = parseAstSizeValue(prop)
|
|
192
|
+
}
|
|
193
|
+
return acc
|
|
194
|
+
}
|
|
195
|
+
if (ast.isPaddingSizeProperty(prop)) {
|
|
196
|
+
if (isTruthy(prop.value)) {
|
|
197
|
+
acc.custom[prop.key] = parseAstSizeValue(prop)
|
|
198
|
+
}
|
|
199
|
+
return acc
|
|
200
|
+
}
|
|
183
201
|
nonexhaustive(prop)
|
|
184
202
|
},
|
|
185
203
|
{
|
|
@@ -3,18 +3,18 @@ import { nonexhaustive } from '@likec4/core'
|
|
|
3
3
|
import type { AstNode } from 'langium'
|
|
4
4
|
import {
|
|
5
5
|
type AstNodeDescription,
|
|
6
|
+
type ReferenceInfo,
|
|
7
|
+
type Scope,
|
|
8
|
+
type Stream,
|
|
6
9
|
AstUtils,
|
|
7
10
|
DefaultScopeProvider,
|
|
8
11
|
DONE_RESULT,
|
|
9
12
|
EMPTY_SCOPE,
|
|
10
13
|
EMPTY_STREAM,
|
|
11
14
|
MapScope,
|
|
12
|
-
type ReferenceInfo,
|
|
13
|
-
type Scope,
|
|
14
|
-
type Stream,
|
|
15
15
|
stream,
|
|
16
16
|
StreamImpl,
|
|
17
|
-
StreamScope
|
|
17
|
+
StreamScope,
|
|
18
18
|
} from 'langium'
|
|
19
19
|
import { ast } from '../ast'
|
|
20
20
|
import { logger } from '../logger'
|
|
@@ -54,7 +54,7 @@ export class LikeC4ScopeProvider extends DefaultScopeProvider {
|
|
|
54
54
|
return iterator.next()
|
|
55
55
|
}
|
|
56
56
|
return DONE_RESULT
|
|
57
|
-
}
|
|
57
|
+
},
|
|
58
58
|
)
|
|
59
59
|
}
|
|
60
60
|
|
|
@@ -99,7 +99,7 @@ export class LikeC4ScopeProvider extends DefaultScopeProvider {
|
|
|
99
99
|
return this.getGlobalScope(referenceType, context)
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
if (ast.
|
|
102
|
+
if (ast.isStrictFqnElementRef(container) && context.property === 'el') {
|
|
103
103
|
const parent = container.parent
|
|
104
104
|
if (!parent) {
|
|
105
105
|
return this.getGlobalScope(referenceType, context)
|
|
@@ -116,7 +116,7 @@ export class LikeC4ScopeProvider extends DefaultScopeProvider {
|
|
|
116
116
|
const closestElement = AstUtils.getContainerOfType(container, ast.isElement)
|
|
117
117
|
if (closestElement) {
|
|
118
118
|
return new MapScope([
|
|
119
|
-
this.descriptions.createDescription(closestElement, context.reference.$refText)
|
|
119
|
+
this.descriptions.createDescription(closestElement, context.reference.$refText),
|
|
120
120
|
])
|
|
121
121
|
} else {
|
|
122
122
|
return EMPTY_SCOPE
|
|
@@ -146,8 +146,8 @@ export class LikeC4ScopeProvider extends DefaultScopeProvider {
|
|
|
146
146
|
// Third preference for elements if we are in deployment view
|
|
147
147
|
AstUtils.hasContainerOfType(container, ast.isDeploymentView)
|
|
148
148
|
? this.computeScope(context, ast.Element)
|
|
149
|
-
: EMPTY_SCOPE
|
|
150
|
-
)
|
|
149
|
+
: EMPTY_SCOPE,
|
|
150
|
+
),
|
|
151
151
|
)
|
|
152
152
|
}
|
|
153
153
|
const parentRef = parent.value.ref
|
package/src/test/testServices.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { LikeC4Model } from '@likec4/core'
|
|
1
2
|
import { DocumentState, EmptyFileSystem, TextDocument } from 'langium'
|
|
2
3
|
import * as assert from 'node:assert'
|
|
3
4
|
import stripIndent from 'strip-indent'
|
|
@@ -16,7 +17,7 @@ export function createTestServices(workspace = 'file:///test/workspace') {
|
|
|
16
17
|
const formatter = services.lsp.Formatter
|
|
17
18
|
const workspaceFolder = {
|
|
18
19
|
name: 'test',
|
|
19
|
-
uri: workspaceUri.toString()
|
|
20
|
+
uri: workspaceUri.toString(),
|
|
20
21
|
}
|
|
21
22
|
let isInitialized = false
|
|
22
23
|
let documentIndex = 1
|
|
@@ -32,7 +33,7 @@ export function createTestServices(workspace = 'file:///test/workspace') {
|
|
|
32
33
|
capabilities: {},
|
|
33
34
|
processId: null,
|
|
34
35
|
rootUri: null,
|
|
35
|
-
workspaceFolders: [workspaceFolder]
|
|
36
|
+
workspaceFolders: [workspaceFolder],
|
|
36
37
|
})
|
|
37
38
|
await services.shared.workspace.WorkspaceManager.initializeWorkspace([workspaceFolder])
|
|
38
39
|
})
|
|
@@ -40,11 +41,11 @@ export function createTestServices(workspace = 'file:///test/workspace') {
|
|
|
40
41
|
const docUri = Utils.resolvePath(
|
|
41
42
|
workspaceUri,
|
|
42
43
|
'./src/',
|
|
43
|
-
uri ?? `${documentIndex++}${metaData.fileExtensions[0]}
|
|
44
|
+
uri ?? `${documentIndex++}${metaData.fileExtensions[0]}`,
|
|
44
45
|
)
|
|
45
46
|
const document = services.shared.workspace.LangiumDocumentFactory.fromString(
|
|
46
47
|
stripIndent(input),
|
|
47
|
-
docUri
|
|
48
|
+
docUri,
|
|
48
49
|
)
|
|
49
50
|
langiumDocuments.addDocument(document)
|
|
50
51
|
await services.shared.workspace.WorkspaceLock.write(async (_cancelToken) => {
|
|
@@ -65,7 +66,7 @@ export function createTestServices(workspace = 'file:///test/workspace') {
|
|
|
65
66
|
document,
|
|
66
67
|
diagnostics,
|
|
67
68
|
warnings,
|
|
68
|
-
errors
|
|
69
|
+
errors,
|
|
69
70
|
}
|
|
70
71
|
}
|
|
71
72
|
|
|
@@ -79,8 +80,8 @@ export function createTestServices(workspace = 'file:///test/workspace') {
|
|
|
79
80
|
document,
|
|
80
81
|
{
|
|
81
82
|
options: { tabSize: 2, insertSpaces: true },
|
|
82
|
-
textDocument: { uri: document.uri.toString() }
|
|
83
|
-
}
|
|
83
|
+
textDocument: { uri: document.uri.toString() },
|
|
84
|
+
},
|
|
84
85
|
)
|
|
85
86
|
|
|
86
87
|
return TextDocument.applyEdits(document.textDocument, edits ?? [])
|
|
@@ -106,7 +107,7 @@ export function createTestServices(workspace = 'file:///test/workspace') {
|
|
|
106
107
|
return {
|
|
107
108
|
diagnostics,
|
|
108
109
|
errors,
|
|
109
|
-
warnings
|
|
110
|
+
warnings,
|
|
110
111
|
}
|
|
111
112
|
}
|
|
112
113
|
|
|
@@ -117,6 +118,13 @@ export function createTestServices(workspace = 'file:///test/workspace') {
|
|
|
117
118
|
return model
|
|
118
119
|
}
|
|
119
120
|
|
|
121
|
+
const buildLikeC4Model = async () => {
|
|
122
|
+
await validateAll()
|
|
123
|
+
const model = await modelBuilder.buildComputedModel()
|
|
124
|
+
if (!model) throw new Error('No model found')
|
|
125
|
+
return LikeC4Model.create(model)
|
|
126
|
+
}
|
|
127
|
+
|
|
120
128
|
/**
|
|
121
129
|
* This will clear all documents
|
|
122
130
|
*/
|
|
@@ -133,8 +141,9 @@ export function createTestServices(workspace = 'file:///test/workspace') {
|
|
|
133
141
|
validate,
|
|
134
142
|
validateAll,
|
|
135
143
|
buildModel,
|
|
144
|
+
buildLikeC4Model,
|
|
136
145
|
resetState,
|
|
137
|
-
format
|
|
146
|
+
format,
|
|
138
147
|
}
|
|
139
148
|
}
|
|
140
149
|
|