@likec4/language-server 1.12.1 → 1.13.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.
Files changed (43) hide show
  1. package/dist/browser.cjs +1 -1
  2. package/dist/browser.d.cts +2 -2
  3. package/dist/browser.d.mts +2 -2
  4. package/dist/browser.d.ts +2 -2
  5. package/dist/browser.mjs +2 -2
  6. package/dist/index.cjs +1 -1
  7. package/dist/index.d.cts +2 -2
  8. package/dist/index.d.mts +2 -2
  9. package/dist/index.d.ts +2 -2
  10. package/dist/index.mjs +2 -2
  11. package/dist/model-graph/index.cjs +1 -1
  12. package/dist/model-graph/index.d.cts +1 -1
  13. package/dist/model-graph/index.d.mts +1 -1
  14. package/dist/model-graph/index.d.ts +1 -1
  15. package/dist/model-graph/index.mjs +1 -1
  16. package/dist/shared/{language-server.CzkWpuWT.d.mts → language-server.B-9_mDoo.d.mts} +7 -2
  17. package/dist/shared/{language-server.BH0brgLf.cjs → language-server.C2ebP2pZ.cjs} +80 -90
  18. package/dist/shared/{language-server.DJAHXekh.d.cts → language-server.C3oS5yhF.d.cts} +7 -2
  19. package/dist/shared/{language-server.3Bh0zvVS.cjs → language-server.CbDa016p.cjs} +9 -7
  20. package/dist/shared/{language-server.DtLmc4Fz.d.cts → language-server.Cnq_hgfm.d.cts} +6 -1
  21. package/dist/shared/{language-server.BYjS7OIz.mjs → language-server.CrE0nFSB.mjs} +79 -89
  22. package/dist/shared/{language-server.BxxqS4Id.mjs → language-server.DFLaUdYu.mjs} +7 -6
  23. package/dist/shared/{language-server.BCDM5gt9.d.ts → language-server.eY70DuKx.d.ts} +6 -1
  24. package/dist/shared/{language-server.B8Ce0R_D.d.ts → language-server.r5AXAWzc.d.ts} +7 -2
  25. package/dist/shared/{language-server.BN7V1vQA.d.mts → language-server.ryB8CivX.d.mts} +6 -1
  26. package/package.json +7 -7
  27. package/src/ast.ts +1 -1
  28. package/src/generated/ast.ts +3 -1
  29. package/src/generated/grammar.ts +1 -1
  30. package/src/like-c4.langium +4 -2
  31. package/src/lsp/CompletionProvider.ts +2 -2
  32. package/src/model/model-parser.ts +78 -69
  33. package/src/model-change/ModelChanges.ts +0 -28
  34. package/src/model-change/changeViewLayout.ts +5 -5
  35. package/src/model-graph/compute-view/compute.ts +1 -1
  36. package/src/model-graph/dynamic-view/compute.ts +1 -1
  37. package/src/model-graph/utils/sortNodes.ts +3 -3
  38. package/src/test/testServices.ts +9 -5
  39. package/src/utils/graphlib.ts +5 -7
  40. package/src/validation/_shared.ts +2 -1
  41. package/src/validation/index.ts +0 -2
  42. package/src/validation/specification.ts +0 -11
  43. package/src/view-utils/resolve-extended-views.ts +2 -2
@@ -194,8 +194,10 @@ MetadataAttribute:
194
194
  // Views -------------------------------------
195
195
 
196
196
  ModelViews:
197
- name='views' '{'
198
- views+=LikeC4ViewRule*
197
+ name='views' '{' (
198
+ views+=LikeC4ViewRule |
199
+ styles+=ViewRuleStyle
200
+ )*
199
201
  '}';
200
202
 
201
203
  type LikeC4View = ElementView | DynamicView;
@@ -23,10 +23,10 @@ export class LikeC4CompletionProvider extends DefaultCompletionProvider {
23
23
  if (['views', 'specification', 'model'].includes(keyword.value)) {
24
24
  return acceptor(context, {
25
25
  label: keyword.value,
26
- detail: `Insert ${keyword} block`,
26
+ detail: `Insert ${keyword.value} block`,
27
27
  kind: CompletionItemKind.Module,
28
28
  insertTextFormat: InsertTextFormat.Snippet,
29
- insertText: `${keyword} {\n\t$0\n}`
29
+ insertText: `${keyword.value} {\n\t$0\n}`
30
30
  })
31
31
  }
32
32
  if (keyword.value === 'dynamic') {
@@ -36,6 +36,9 @@ import { stringHash } from '../utils'
36
36
  import { deserializeFromComment, hasManualLayout } from '../view-utils/manual-layout'
37
37
  import type { FqnIndex } from './fqn-index'
38
38
  import { parseWhereClause } from './model-parser-where'
39
+ import {
40
+ type NotationProperty
41
+ } from '../generated/ast'
39
42
 
40
43
  const { getDocument } = AstUtils
41
44
 
@@ -283,17 +286,23 @@ export class LikeC4ModelParser {
283
286
  }
284
287
 
285
288
  private parseViews(doc: ParsedLikeC4LangiumDocument, isValid: IsValidFn) {
286
- const views = doc.parseResult.value.views.flatMap(v => isValid(v) ? v.views : [])
287
- for (const view of views) {
288
- try {
289
- if (!isValid(view)) {
290
- continue
289
+ const viewBlocks = doc.parseResult.value.views.filter(v => isValid(v))
290
+ for (const viewBlock of viewBlocks) {
291
+ const localStyles = viewBlock.styles
292
+ .flatMap(s => this.parseViewRuleStyle(s, isValid))
293
+ const stylesToApply = localStyles
294
+
295
+ for (const view of viewBlock.views) {
296
+ try {
297
+ if (!isValid(view)) {
298
+ continue
299
+ }
300
+ doc.c4Views.push(
301
+ ast.isElementView(view) ? this.parseElementView(view, stylesToApply, isValid) : this.parseDynamicElementView(view, stylesToApply, isValid)
302
+ )
303
+ } catch (e) {
304
+ logWarnError(e)
291
305
  }
292
- doc.c4Views.push(
293
- ast.isElementView(view) ? this.parseElementView(view, isValid) : this.parseDynamicElementView(view, isValid)
294
- )
295
- } catch (e) {
296
- logWarnError(e)
297
306
  }
298
307
  }
299
308
  }
@@ -596,16 +605,7 @@ export class LikeC4ModelParser {
596
605
  return this.parseViewRulePredicate(astRule, isValid)
597
606
  }
598
607
  if (ast.isViewRuleStyle(astRule)) {
599
- const styleProps = toElementStyle(astRule.props.filter(ast.isStyleProperty), isValid)
600
- const notation = removeIndent(astRule.props.find(ast.isNotationProperty)?.value)
601
- const targets = this.parseElementExpressionsIterator(astRule.target)
602
- return {
603
- targets,
604
- ...(notation && { notation }),
605
- style: {
606
- ...styleProps
607
- }
608
- }
608
+ return this.parseViewRuleStyle(astRule, isValid)
609
609
  }
610
610
  if (ast.isViewRuleAutoLayout(astRule)) {
611
611
  return toAutoLayout(astRule)
@@ -613,6 +613,26 @@ export class LikeC4ModelParser {
613
613
  nonexhaustive(astRule)
614
614
  }
615
615
 
616
+ private parseViewRuleStyle(astRule: ast.ViewRuleStyle, isValid: IsValidFn): c4.ViewRuleStyle {
617
+ const styleProps = astRule.props.filter(ast.isStyleProperty)
618
+ const targets = astRule.target
619
+ const notation = astRule.props.find(ast.isNotationProperty)
620
+ return this.parseRuleStyle(styleProps, targets, isValid, notation)
621
+ }
622
+
623
+ private parseRuleStyle(styleProperties: ast.StyleProperty[], elementExpressionsIterator: ast.ElementExpressionsIterator, isValid: IsValidFn, notationProperty?: NotationProperty): c4.ViewRuleStyle {
624
+ const styleProps = toElementStyle(styleProperties, isValid)
625
+ const notation = removeIndent(notationProperty?.value)
626
+ const targets = this.parseElementExpressionsIterator(elementExpressionsIterator)
627
+ return {
628
+ targets,
629
+ ...(notation && { notation }),
630
+ style: {
631
+ ...styleProps
632
+ }
633
+ }
634
+ }
635
+
616
636
  private parseViewManualLaout(node: ast.DynamicView | ast.ElementView): c4.ViewManualLayout | undefined {
617
637
  const commentNode = CstUtils.findCommentNode(node.$cstNode, ['BLOCK_COMMENT'])
618
638
  if (!commentNode || !hasManualLayout(commentNode.text)) {
@@ -709,7 +729,7 @@ export class LikeC4ModelParser {
709
729
  return step
710
730
  }
711
731
 
712
- private parseElementView(astNode: ast.ElementView, isValid: IsValidFn): ParsedAstElementView {
732
+ private parseElementView(astNode: ast.ElementView, additionalStyles: c4.ViewRuleStyle[], isValid: IsValidFn): ParsedAstElementView {
713
733
  const body = astNode.body
714
734
  invariant(body, 'ElementView body is not defined')
715
735
  const astPath = this.getAstNodePath(astNode)
@@ -750,14 +770,14 @@ export class LikeC4ModelParser {
750
770
  description,
751
771
  tags,
752
772
  links: isNonEmptyArray(links) ? links : null,
753
- rules: body.rules.flatMap(n => {
773
+ rules: [...additionalStyles, ...body.rules.flatMap(n => {
754
774
  try {
755
775
  return isValid(n) ? this.parseViewRule(n, isValid) : []
756
776
  } catch (e) {
757
777
  logWarnError(e)
758
778
  return []
759
779
  }
760
- }),
780
+ })],
761
781
  ...(viewOf && { viewOf }),
762
782
  ...(manualLayout && { manualLayout })
763
783
  }
@@ -774,7 +794,7 @@ export class LikeC4ModelParser {
774
794
  return view
775
795
  }
776
796
 
777
- private parseDynamicElementView(astNode: ast.DynamicView, isValid: IsValidFn): ParsedAstDynamicView {
797
+ private parseDynamicElementView(astNode: ast.DynamicView, additionalStyles: c4.ViewRuleStyle[], isValid: IsValidFn): ParsedAstDynamicView {
778
798
  const body = astNode.body
779
799
  invariant(body, 'DynamicElementView body is not defined')
780
800
  // only valid props
@@ -807,55 +827,14 @@ export class LikeC4ModelParser {
807
827
  description,
808
828
  tags,
809
829
  links: isNonEmptyArray(links) ? links : null,
810
- rules: body.rules.reduce((acc, n) => {
811
- if (!isValid(n)) {
812
- return acc
813
- }
830
+ rules: [...additionalStyles, ...body.rules.flatMap(n => {
814
831
  try {
815
- if (ast.isDynamicViewIncludePredicate(n)) {
816
- const include = [] as c4.ElementPredicateExpression[]
817
- let iter: ast.DynamicViewPredicateIterator | undefined = n.predicates
818
- while (iter) {
819
- try {
820
- if (isValid(iter.value as any)) {
821
- const c4expr = this.parseElementPredicate(iter.value, isValid)
822
- include.unshift(c4expr)
823
- }
824
- } catch (e) {
825
- logWarnError(e)
826
- }
827
- iter = iter.prev
828
- }
829
- if (include.length > 0) {
830
- acc.push({ include })
831
- }
832
- return acc
833
- }
834
- if (ast.isViewRuleStyle(n)) {
835
- const styleProps = toElementStyle(n.props.filter(ast.isStyleProperty), isValid)
836
- const notation = removeIndent(n.props.find(ast.isNotationProperty)?.value)
837
- const targets = this.parseElementExpressionsIterator(n.target)
838
- if (targets.length > 0) {
839
- acc.push({
840
- targets,
841
- ...(notation && { notation }),
842
- style: {
843
- ...styleProps
844
- }
845
- })
846
- }
847
- return acc
848
- }
849
- if (ast.isViewRuleAutoLayout(n)) {
850
- acc.push(toAutoLayout(n))
851
- return acc
852
- }
853
- nonexhaustive(n)
832
+ return isValid(n) ? this.parseDynamicViewRule(n, isValid) : []
854
833
  } catch (e) {
855
834
  logWarnError(e)
856
- return acc
835
+ return []
857
836
  }
858
- }, [] as Array<c4.DynamicViewRule>),
837
+ }, [] as Array<c4.DynamicViewRule>)],
859
838
  steps: body.steps.reduce((acc, n) => {
860
839
  try {
861
840
  if (isValid(n)) {
@@ -874,6 +853,36 @@ export class LikeC4ModelParser {
874
853
  }
875
854
  }
876
855
 
856
+ private parseDynamicViewRule(astRule: ast.DynamicViewRule, isValid: IsValidFn): c4.DynamicViewRule {
857
+ if (ast.isDynamicViewIncludePredicate(astRule)) {
858
+ return this.parseDynamicViewIncludePredicate(astRule, isValid)
859
+ }
860
+ if (ast.isViewRuleStyle(astRule)) {
861
+ return this.parseViewRuleStyle(astRule, isValid)
862
+ }
863
+ if (ast.isViewRuleAutoLayout(astRule)) {
864
+ return toAutoLayout(astRule)
865
+ }
866
+ nonexhaustive(astRule)
867
+ }
868
+
869
+ private parseDynamicViewIncludePredicate(astRule: ast.DynamicViewIncludePredicate, isValid: IsValidFn): c4.DynamicViewIncludeRule {
870
+ const include = [] as c4.ElementPredicateExpression[]
871
+ let iter: ast.DynamicViewPredicateIterator | undefined = astRule.predicates
872
+ while (iter) {
873
+ try {
874
+ if (isValid(iter.value as any)) {
875
+ const c4expr = this.parseElementPredicate(iter.value, isValid)
876
+ include.unshift(c4expr)
877
+ }
878
+ } catch (e) {
879
+ logWarnError(e)
880
+ }
881
+ iter = iter.prev
882
+ }
883
+ return { include }
884
+ }
885
+
877
886
  protected resolveFqn(node: ast.Element | ast.ExtendElement) {
878
887
  if (ast.isExtendElement(node)) {
879
888
  return getFqnElementRef(node.element)
@@ -9,34 +9,6 @@ import { changeElementStyle } from './changeElementStyle'
9
9
  import { changeViewLayout } from './changeViewLayout'
10
10
  import { saveManualLayout } from './saveManualLayout'
11
11
 
12
- // function unionRangeOfAllEdits(ranges: Range[]): Range {
13
- // let startLine = Number.MAX_SAFE_INTEGER
14
- // let endLine = Number.MIN_SAFE_INTEGER
15
-
16
- // let startCharacter = Number.MAX_SAFE_INTEGER
17
- // let endCharacter = Number.MIN_SAFE_INTEGER
18
-
19
- // for (const { start, end } of ranges) {
20
- // if (start.line <= startLine) {
21
- // if (startLine == start.line) {
22
- // startCharacter = Math.min(start.character, startCharacter)
23
- // } else {
24
- // startLine = start.line
25
- // startCharacter = start.character
26
- // }
27
- // }
28
- // if (endLine <= end.line) {
29
- // if (endLine == end.line) {
30
- // endCharacter = Math.max(end.character, endCharacter)
31
- // } else {
32
- // endLine = end.line
33
- // endCharacter = end.character
34
- // }
35
- // }
36
- // }
37
- // return Range.create(startLine, startCharacter, endLine, endCharacter)
38
- // }
39
-
40
12
  export class LikeC4ModelChanges {
41
13
  private locator: LikeC4ModelLocator
42
14
 
@@ -1,6 +1,6 @@
1
- import { invariant, type ViewRuleAutoLayout } from '@likec4/core'
1
+ import { invariant, type ViewChange } from '@likec4/core'
2
2
  import { GrammarUtils } from 'langium'
3
- import { isDefined } from 'remeda'
3
+ import { isNumber } from 'remeda'
4
4
  import { TextEdit } from 'vscode-languageserver-types'
5
5
  import { ast, type ParsedAstView, type ParsedLikeC4LangiumDocument, toAstViewLayoutDirection } from '../ast'
6
6
  import type { LikeC4Services } from '../module'
@@ -11,7 +11,7 @@ type ChangeViewLayoutArg = {
11
11
  view: ParsedAstView
12
12
  doc: ParsedLikeC4LangiumDocument
13
13
  viewAst: ast.LikeC4View
14
- layout: ViewRuleAutoLayout
14
+ layout: ViewChange.ChangeAutoLayout['layout']
15
15
  }
16
16
 
17
17
  export function changeViewLayout(_services: LikeC4Services, {
@@ -28,9 +28,9 @@ export function changeViewLayout(_services: LikeC4Services, {
28
28
 
29
29
  let newRule = `autoLayout ${newdirection}`
30
30
 
31
- if (isDefined(layout.rankSep)) {
31
+ if (isNumber(layout.rankSep)) {
32
32
  newRule += ` ${layout.rankSep}`
33
- if (isDefined(layout.nodeSep)) {
33
+ if (isNumber(layout.nodeSep)) {
34
34
  newRule += ` ${layout.nodeSep}`
35
35
  }
36
36
  }
@@ -178,7 +178,7 @@ export class ComputeCtx {
178
178
  autoLayout: {
179
179
  direction: autoLayoutRule?.direction ?? 'TB',
180
180
  ...(autoLayoutRule?.nodeSep && { nodeSep: autoLayoutRule.nodeSep }),
181
- ...(autoLayoutRule?.rankSep && { rankSep: autoLayoutRule.rankSep }),
181
+ ...(autoLayoutRule?.rankSep && { rankSep: autoLayoutRule.rankSep })
182
182
  },
183
183
  nodes: map(nodes, omit(['notation'])),
184
184
  edges: applyCustomRelationProperties(rules, nodes, sortedEdges),
@@ -203,7 +203,7 @@ export class DynamicViewComputeCtx {
203
203
  autoLayout: {
204
204
  direction: autoLayoutRule?.direction ?? 'LR',
205
205
  ...(autoLayoutRule?.nodeSep && { nodeSep: autoLayoutRule.nodeSep }),
206
- ...(autoLayoutRule?.rankSep && { rankSep: autoLayoutRule.rankSep }),
206
+ ...(autoLayoutRule?.rankSep && { rankSep: autoLayoutRule.rankSep })
207
207
  },
208
208
  nodes: map(nodes, omit(['notation'])),
209
209
  edges,
@@ -8,7 +8,7 @@ import {
8
8
  invariant,
9
9
  nonNullable
10
10
  } from '@likec4/core'
11
- import { difference, filter, map, pipe, sort, take } from 'remeda'
11
+ import { difference, filter, map, pipe, sort } from 'remeda'
12
12
  import { Graph, postorder } from '../../utils/graphlib'
13
13
 
14
14
  // side effect
@@ -53,7 +53,7 @@ export function sortNodes({
53
53
  }
54
54
 
55
55
  for (const n of nodes) {
56
- g.setNode(n.id)
56
+ g.setNode(n.id, n.id)
57
57
  if (n.children.length > 0) {
58
58
  // n.children.forEach(c => {
59
59
  // g.setEdge(n.id, c, undefined, `${n.id}:${c}`)
@@ -79,7 +79,7 @@ export function sortNodes({
79
79
  }
80
80
  }
81
81
 
82
- let sources = g.sources()
82
+ let sources = g.sources() as unknown as Fqn[]
83
83
  if (sources.length === 0) {
84
84
  sources = pipe(
85
85
  nodes,
@@ -75,14 +75,15 @@ export function createTestServices(workspace = 'file:///test/workspace') {
75
75
  await documentBuilder.build([document], { validation: true })
76
76
  })
77
77
 
78
- const edits = await services.lsp.Formatter?.formatDocument(
79
- document,
78
+ const edits = await formatter?.formatDocument(
79
+ document,
80
80
  {
81
- options: {tabSize: 2, insertSpaces: true},
81
+ options: { tabSize: 2, insertSpaces: true },
82
82
  textDocument: { uri: document.uri.toString() }
83
- });
83
+ }
84
+ )
84
85
 
85
- return TextDocument.applyEdits(document.textDocument, edits ?? []);
86
+ return TextDocument.applyEdits(document.textDocument, edits ?? [])
86
87
  }
87
88
 
88
89
  type ValidateAllResult = {
@@ -116,6 +117,9 @@ export function createTestServices(workspace = 'file:///test/workspace') {
116
117
  return model
117
118
  }
118
119
 
120
+ /**
121
+ * This will clear all documents
122
+ */
119
123
  const resetState = async () => {
120
124
  await services.shared.workspace.WorkspaceLock.write(async (cancelToken) => {
121
125
  const docs = langiumDocuments.all.toArray().map(doc => doc.uri)
@@ -1,11 +1,9 @@
1
- // '@dagrejs/graphlib' is a CommonJS module
2
- // Here is a workaround to import it
1
+ import dagre from '@dagrejs/dagre'
3
2
 
4
- import { Graph } from '@dagrejs/graphlib'
5
- import graphlib from '@dagrejs/graphlib'
3
+ const Graph = dagre.graphlib.Graph
6
4
 
7
5
  export { Graph }
8
6
 
9
- export const postorder = graphlib.alg.postorder
10
- export const findCycles = graphlib.alg.findCycles
11
- export const isAcyclic = graphlib.alg.isAcyclic
7
+ export const postorder = dagre.graphlib.alg.postorder
8
+ export const findCycles = dagre.graphlib.alg.findCycles
9
+ export const isAcyclic = dagre.graphlib.alg.isAcyclic
@@ -7,7 +7,8 @@ export const RESERVED_WORDS = [
7
7
  'it',
8
8
  'self',
9
9
  'super',
10
- 'likec4lib'
10
+ 'likec4lib',
11
+ 'global'
11
12
  ]
12
13
 
13
14
  export function tryOrLog<T extends AstNode>(fn: ValidationCheck<T>): ValidationCheck<T> {
@@ -9,7 +9,6 @@ import { relationBodyChecks, relationChecks } from './relation'
9
9
  import {
10
10
  elementKindChecks,
11
11
  modelRuleChecks,
12
- modelViewsChecks,
13
12
  relationshipChecks,
14
13
  specificationRuleChecks,
15
14
  tagChecks
@@ -32,7 +31,6 @@ export function registerValidationChecks(services: LikeC4Services) {
32
31
  IconProperty: iconPropertyRuleChecks(services),
33
32
  SpecificationRule: specificationRuleChecks(services),
34
33
  Model: modelRuleChecks(services),
35
- ModelViews: modelViewsChecks(services),
36
34
  DynamicViewStep: dynamicViewStep(services),
37
35
  LikeC4View: viewChecks(services),
38
36
  Element: elementChecks(services),
@@ -27,17 +27,6 @@ export const modelRuleChecks = (_: LikeC4Services): ValidationCheck<ast.Model> =
27
27
  }
28
28
  }
29
29
 
30
- export const modelViewsChecks = (_: LikeC4Services): ValidationCheck<ast.ModelViews> => {
31
- return (node, accept) => {
32
- if (node.$containerIndex && node.$containerIndex > 0) {
33
- accept('warning', `Prefer one views block per document`, {
34
- node: node,
35
- property: 'name'
36
- })
37
- }
38
- }
39
- }
40
-
41
30
  export const elementKindChecks = (services: LikeC4Services): ValidationCheck<ast.ElementKind> => {
42
31
  const index = services.shared.workspace.IndexManager
43
32
  return tryOrLog((node, accept) => {
@@ -17,7 +17,7 @@ export function resolveRulesExtendedViews<V extends Record<any, LikeC4View>>(
17
17
  compound: false
18
18
  })
19
19
  for (const view of values(unresolvedViews)) {
20
- g.setNode(view.id)
20
+ g.setNode(view.id, view.id)
21
21
  if (isExtendsElementView(view)) {
22
22
  // view -> parent
23
23
  g.setEdge(view.id, view.extends)
@@ -40,7 +40,7 @@ export function resolveRulesExtendedViews<V extends Record<any, LikeC4View>>(
40
40
  g.removeNode(cycledNode)
41
41
  }
42
42
 
43
- const ordered = postorder(g, g.sources())
43
+ const ordered = postorder(g, g.sources() as unknown as string[])
44
44
 
45
45
  return ordered.reduce((acc, id) => {
46
46
  const view = unresolvedViews[id]