@likec4/language-server 0.34.0 → 0.35.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/dist/ast.d.ts CHANGED
@@ -55,7 +55,7 @@ export declare const ElementOps: {
55
55
  };
56
56
  export interface DocFqnIndexEntry {
57
57
  name: string;
58
- el: ast.Element;
58
+ el: WeakRef<ast.Element>;
59
59
  path: string;
60
60
  }
61
61
  export interface LikeC4DocumentProps {
package/dist/ast.js CHANGED
@@ -119,7 +119,7 @@ export function resolveRelationPoints(node) {
119
119
  };
120
120
  }
121
121
  if (!ast.isElementBody(node.$container)) {
122
- throw new RelationRefError('Invalid relation parent');
122
+ throw new RelationRefError('Invalid relation parent, expected Element');
123
123
  }
124
124
  return {
125
125
  source: node.$container.$container,
@@ -1,5 +1,5 @@
1
1
  /******************************************************************************
2
- * This file was generated by langium-cli 2.0.0.
2
+ * This file was generated by langium-cli 2.0.1.
3
3
  * DO NOT EDIT MANUALLY!
4
4
  ******************************************************************************/
5
5
  import type { AstNode, Reference, ReferenceInfo, TypeMetaData } from 'langium';
@@ -1,5 +1,5 @@
1
1
  /******************************************************************************
2
- * This file was generated by langium-cli 2.0.0.
2
+ * This file was generated by langium-cli 2.0.1.
3
3
  * DO NOT EDIT MANUALLY!
4
4
  ******************************************************************************/
5
5
  import { AbstractAstReflection } from 'langium';
@@ -1,5 +1,5 @@
1
1
  /******************************************************************************
2
- * This file was generated by langium-cli 2.0.0.
2
+ * This file was generated by langium-cli 2.0.1.
3
3
  * DO NOT EDIT MANUALLY!
4
4
  ******************************************************************************/
5
5
  import type { Grammar } from 'langium';
@@ -1,5 +1,5 @@
1
1
  /******************************************************************************
2
- * This file was generated by langium-cli 2.0.0.
2
+ * This file was generated by langium-cli 2.0.1.
3
3
  * DO NOT EDIT MANUALLY!
4
4
  ******************************************************************************/
5
5
  import { loadGrammarFromJson } from 'langium';
@@ -1,5 +1,5 @@
1
1
  /******************************************************************************
2
- * This file was generated by langium-cli 2.0.0.
2
+ * This file was generated by langium-cli 2.0.1.
3
3
  * DO NOT EDIT MANUALLY!
4
4
  ******************************************************************************/
5
5
  import type { LangiumGeneratedServices, LangiumGeneratedSharedServices, LangiumSharedServices, LangiumServices, Module, IParserConfig } from 'langium';
@@ -1,5 +1,5 @@
1
1
  /******************************************************************************
2
- * This file was generated by langium-cli 2.0.0.
2
+ * This file was generated by langium-cli 2.0.1.
3
3
  * DO NOT EDIT MANUALLY!
4
4
  ******************************************************************************/
5
5
  import { LikeC4AstReflection } from './ast.js';
@@ -28,7 +28,7 @@ export function computeDocumentFqn(document, services) {
28
28
  const fqn = AsFqn(el.name, parent);
29
29
  const path = locator.getAstNodePath(el);
30
30
  c4fqns.add(fqn, {
31
- el,
31
+ el: new WeakRef(el),
32
32
  path,
33
33
  name: el.name
34
34
  });
@@ -20,7 +20,7 @@ export declare class FqnIndex {
20
20
  private documents;
21
21
  private entries;
22
22
  getFqn(el: ast.Element): Fqn | null;
23
- byFqn(fqn: Fqn): Stream<import("../ast").DocFqnIndexEntry>;
23
+ byFqn(fqn: Fqn): Stream<FqnIndexEntry>;
24
24
  directChildrenOf(parent: Fqn): Stream<FqnIndexEntry>;
25
25
  /**
26
26
  * Returns descedant elements with unique names in the scope
@@ -52,7 +52,16 @@ export class FqnIndex {
52
52
  return this.langiumDocuments.all.filter(isFqnIndexedDocument);
53
53
  }
54
54
  entries() {
55
- return this.documents().flatMap(doc => doc.c4fqns.entries().map(([fqn, el]) => ({ fqn, doc, ...el })));
55
+ return this.documents().flatMap(doc => doc.c4fqns
56
+ .entries()
57
+ .map(([fqn, entry]) => {
58
+ const el = entry.el.deref();
59
+ if (el) {
60
+ return { ...entry, fqn, el, doc };
61
+ }
62
+ return null;
63
+ })
64
+ .nonNullable());
56
65
  }
57
66
  getFqn(el) {
58
67
  return el.fqn ?? ElementOps.readId(el) ?? null;
@@ -70,7 +79,13 @@ export class FqnIndex {
70
79
  }
71
80
  byFqn(fqn) {
72
81
  return this.documents().flatMap(doc => {
73
- return doc.c4fqns.get(fqn);
82
+ return doc.c4fqns.get(fqn).flatMap(entry => {
83
+ const el = entry.el.deref();
84
+ if (el) {
85
+ return [{ fqn, el, doc, path: entry.path, name: entry.name }];
86
+ }
87
+ return [];
88
+ });
74
89
  });
75
90
  }
76
91
  directChildrenOf(parent) {
@@ -1,7 +1,7 @@
1
+ import { InvalidModelError } from '@likec4/core';
1
2
  import { findNodeForProperty, getDocument } from 'langium';
2
- import { ElementOps, ast, isParsedLikeC4LangiumDocument } from '../ast';
3
- import { isFqnIndexedDocument } from './fqn-index';
4
- import { nonNullable } from '@likec4/core';
3
+ import { ast, isParsedLikeC4LangiumDocument } from '../ast';
4
+ import {} from './fqn-index';
5
5
  export class LikeC4ModelLocator {
6
6
  services;
7
7
  fqnIndex;
@@ -15,7 +15,7 @@ export class LikeC4ModelLocator {
15
15
  return this.langiumDocuments.all.filter(isParsedLikeC4LangiumDocument);
16
16
  }
17
17
  getParsedElement(astNode) {
18
- const fqn = ElementOps.readId(astNode) ?? null;
18
+ const fqn = this.fqnIndex.getFqn(astNode);
19
19
  if (!fqn)
20
20
  return null;
21
21
  const doc = getDocument(astNode);
@@ -25,22 +25,18 @@ export class LikeC4ModelLocator {
25
25
  return doc.c4Elements.find(e => e.id === fqn) ?? null;
26
26
  }
27
27
  locateElement(fqn, property = 'name') {
28
- for (const doc of this.documents()) {
29
- const entries = doc.c4fqns.get(fqn);
30
- if (entries.length === 0) {
31
- continue;
32
- }
33
- const { el: node } = nonNullable(entries[0]);
34
- const propertyNode = findNodeForProperty(node.$cstNode, property) ?? node.$cstNode;
35
- if (!propertyNode) {
36
- return null;
37
- }
38
- return {
39
- uri: doc.uri.toString(),
40
- range: propertyNode.range
41
- };
28
+ const entry = this.fqnIndex.byFqn(fqn).head();
29
+ if (!entry) {
30
+ return null;
42
31
  }
43
- return null;
32
+ const propertyNode = findNodeForProperty(entry.el.$cstNode, property) ?? entry.el.$cstNode;
33
+ if (!propertyNode) {
34
+ return null;
35
+ }
36
+ return {
37
+ uri: entry.doc.uri.toString(),
38
+ range: propertyNode.range
39
+ };
44
40
  }
45
41
  locateRelation(relationId) {
46
42
  for (const doc of this.documents()) {
@@ -64,6 +60,9 @@ export class LikeC4ModelLocator {
64
60
  };
65
61
  }
66
62
  }
63
+ if (node.arr == null) {
64
+ throw new InvalidModelError('Relation.arr is not defined, but should be');
65
+ }
67
66
  const targetNode = findNodeForProperty(node.$cstNode, 'arr');
68
67
  if (!targetNode) {
69
68
  return null;
@@ -71,7 +70,7 @@ export class LikeC4ModelLocator {
71
70
  return {
72
71
  uri: doc.uri.toString(),
73
72
  range: {
74
- start: targetNode.range.end,
73
+ start: targetNode.range.start,
75
74
  end: targetNode.range.end
76
75
  }
77
76
  };
@@ -49,6 +49,10 @@ export class LikeC4ModelParser {
49
49
  const specs = doc.parseResult.value.specification?.specs.filter(ast.isSpecificationElementKind);
50
50
  if (specs) {
51
51
  for (const { kind, style } of specs) {
52
+ if (kind.name in specification.kinds) {
53
+ logger.warn(`Duplicate specification for kind ${kind.name}`);
54
+ continue;
55
+ }
52
56
  try {
53
57
  specification.kinds[kind.name] = toElementStyleExcludeDefaults(style?.props);
54
58
  }
@@ -246,7 +250,15 @@ export class LikeC4ModelParser {
246
250
  ...(description && { description }),
247
251
  ...(tags && { tags }),
248
252
  ...(links && isNonEmptyArray(links) && { links }),
249
- rules: astNode.rules.map(n => this.parseViewRule(n))
253
+ rules: astNode.rules.flatMap(n => {
254
+ try {
255
+ return this.parseViewRule(n);
256
+ }
257
+ catch (e) {
258
+ logWarnError(e);
259
+ return [];
260
+ }
261
+ })
250
262
  };
251
263
  }
252
264
  resolveFqn(node) {
@@ -1,6 +1,7 @@
1
1
  import { DefaultScopeComputation, MultiMap } from 'langium';
2
2
  import { ast } from '../ast';
3
3
  import { isEmpty } from 'remeda';
4
+ import { nonexhaustive } from '@likec4/core';
4
5
  export class LikeC4ScopeComputation extends DefaultScopeComputation {
5
6
  services;
6
7
  constructor(services) {
@@ -12,15 +13,20 @@ export class LikeC4ScopeComputation extends DefaultScopeComputation {
12
13
  const docExports = [];
13
14
  if (specification && specification.specs.length > 0) {
14
15
  for (const spec of specification.specs) {
15
- if (ast.isSpecificationElementKind(spec) && spec.kind && !isEmpty(spec.kind.name)) {
16
- docExports.push(this.descriptions.createDescription(spec.kind, spec.kind.name, document));
16
+ if (ast.isSpecificationElementKind(spec)) {
17
+ if (spec.kind && !isEmpty(spec.kind.name)) {
18
+ docExports.push(this.descriptions.createDescription(spec.kind, spec.kind.name, document));
19
+ }
17
20
  continue;
18
21
  }
19
- if (ast.isSpecificationTag(spec) && spec.tag && !isEmpty(spec.tag.name)) {
20
- docExports.push(this.descriptions.createDescription(spec.tag, spec.tag.name, document));
21
- docExports.push(this.descriptions.createDescription(spec.tag, '#' + spec.tag.name, document));
22
+ if (ast.isSpecificationTag(spec)) {
23
+ if (spec.tag && !isEmpty(spec.tag.name)) {
24
+ docExports.push(this.descriptions.createDescription(spec.tag, spec.tag.name, document));
25
+ docExports.push(this.descriptions.createDescription(spec.tag, '#' + spec.tag.name, document));
26
+ }
22
27
  continue;
23
28
  }
29
+ nonexhaustive(spec);
24
30
  }
25
31
  }
26
32
  // Only root model elements are exported
@@ -1,11 +1,18 @@
1
- import { DONE_RESULT, DefaultScopeProvider, EMPTY_STREAM, StreamImpl, StreamScope, getDocument, stream } from 'langium';
1
+ import { DONE_RESULT, DefaultScopeProvider, EMPTY_STREAM, StreamImpl, StreamScope, getDocument, stream, findNodeForProperty, toDocumentSegment } from 'langium';
2
2
  import { ast } from '../ast';
3
3
  import { elementRef, isElementRefHead, parentStrictElementRef } from '../elementRef';
4
4
  import { logError } from '../logger';
5
5
  function toAstNodeDescription(entry) {
6
+ const $cstNode = findNodeForProperty(entry.el.$cstNode, 'name');
6
7
  return {
7
8
  documentUri: entry.doc.uri,
8
9
  name: entry.name,
10
+ ...(entry.el.$cstNode && {
11
+ selectionSegment: toDocumentSegment(entry.el.$cstNode)
12
+ }),
13
+ ...($cstNode && {
14
+ nameSegment: toDocumentSegment($cstNode)
15
+ }),
9
16
  path: entry.path,
10
17
  type: ast.Element
11
18
  };
@@ -2,6 +2,7 @@ import { URI } from 'vscode-uri';
2
2
  import { logger, logError } from './logger';
3
3
  import { Rpc } from './protocol';
4
4
  import { nonexhaustive } from '@likec4/core';
5
+ import { isLikeC4LangiumDocument } from './ast';
5
6
  export function registerProtocolHandlers(services) {
6
7
  const connection = services.shared.lsp.Connection;
7
8
  if (!connection) {
@@ -22,7 +23,19 @@ export function registerProtocolHandlers(services) {
22
23
  return Promise.resolve({ model });
23
24
  });
24
25
  connection.onRequest(Rpc.rebuild, async (cancelToken) => {
25
- const changed = LangiumDocuments.all.map(d => d.uri).toArray();
26
+ const changed = LangiumDocuments.all
27
+ .map(d => {
28
+ // clean up any computed properties
29
+ if (isLikeC4LangiumDocument(d)) {
30
+ delete d.c4Specification;
31
+ delete d.c4Elements;
32
+ delete d.c4Relations;
33
+ delete d.c4Views;
34
+ delete d.c4fqns;
35
+ }
36
+ return d.uri;
37
+ })
38
+ .toArray();
26
39
  logger.debug(`[ProtocolHandlers] rebuild all documents: [
27
40
  ${changed.map(d => d.toString()).join('\n ')}
28
41
  ]`);
@@ -29,6 +29,13 @@ export const elementChecks = (services) => {
29
29
  ]
30
30
  });
31
31
  }
32
+ // for (let i = 3; i < el.props.length; i++) {
33
+ // accept('error', `Too many properties, max 3 allowed`, {
34
+ // node: el,
35
+ // property: 'props',
36
+ // index: i
37
+ // })
38
+ // }
32
39
  };
33
40
  };
34
41
  //# sourceMappingURL=element.js.map
@@ -1,5 +1,5 @@
1
1
  import type { ValidationCheck } from 'langium';
2
- import type { ast } from '../ast';
2
+ import { ast } from '../ast';
3
3
  import type { LikeC4Services } from '../module';
4
4
  export declare const relationChecks: (services: LikeC4Services) => ValidationCheck<ast.Relation>;
5
5
  //# sourceMappingURL=relation.d.ts.map
@@ -1,30 +1,43 @@
1
1
  import { isSameHierarchy } from '@likec4/core';
2
- import { resolveRelationPoints } from '../ast';
2
+ import { ast } from '../ast';
3
+ import { elementRef } from '../elementRef';
3
4
  import { logError } from '../logger';
4
5
  export const relationChecks = (services) => {
5
6
  const fqnIndex = services.likec4.FqnIndex;
6
7
  return (el, accept) => {
7
8
  try {
8
- const coupling = resolveRelationPoints(el);
9
- const target = fqnIndex.getFqn(coupling.target);
10
- const source = fqnIndex.getFqn(coupling.source);
11
- if (!target || !source) {
12
- if (!target) {
13
- accept('error', 'Target not found', {
9
+ const targetEl = elementRef(el.target);
10
+ const target = targetEl && fqnIndex.getFqn(targetEl);
11
+ if (!target) {
12
+ accept('error', 'Target not found (not parsed/indexed yet)', {
13
+ node: el,
14
+ property: 'target'
15
+ });
16
+ }
17
+ let sourceEl;
18
+ if ('source' in el) {
19
+ sourceEl = elementRef(el.source);
20
+ }
21
+ else {
22
+ if (!ast.isElementBody(el.$container)) {
23
+ accept('error', 'Invalid relation, expected to have source defined or be inside the element', {
14
24
  node: el,
15
- property: 'target'
25
+ keyword: '->'
16
26
  });
17
27
  }
18
- if (!source) {
19
- accept('error', 'Source not found', {
20
- node: el,
21
- property: 'source'
22
- });
28
+ else {
29
+ sourceEl = el.$container.$container;
23
30
  }
24
- return;
25
31
  }
26
- if (isSameHierarchy(source, target)) {
27
- return accept('error', 'Invalid parent-child relation', {
32
+ const source = sourceEl && fqnIndex.getFqn(sourceEl);
33
+ if (sourceEl && !source) {
34
+ accept('error', 'Source not found (not parsed/indexed yet)', {
35
+ node: el,
36
+ property: 'source'
37
+ });
38
+ }
39
+ if (source && target && isSameHierarchy(source, target)) {
40
+ return accept('error', 'Invalid parent-child relationship', {
28
41
  node: el
29
42
  });
30
43
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@likec4/language-server",
3
3
  "description": "LikeC4 Language Server",
4
- "version": "0.34.0",
4
+ "version": "0.35.0",
5
5
  "license": "MIT",
6
6
  "bugs": "https://github.com/likec4/likec4/issues",
7
7
  "homepage": "https://likec4.dev",
@@ -42,8 +42,8 @@
42
42
  "test:watch": "vitest"
43
43
  },
44
44
  "dependencies": {
45
- "@likec4/core": "0.34.0",
46
- "langium": "^2.0.0",
45
+ "@likec4/core": "0.35.0",
46
+ "langium": "^2.0.2",
47
47
  "nanoid": "^4.0.2",
48
48
  "object-hash": "^3.0.0",
49
49
  "rambdax": "^9.1.1",
@@ -56,9 +56,10 @@
56
56
  "devDependencies": {
57
57
  "@types/node": "^18.15.11",
58
58
  "@types/object-hash": "^3.0.2",
59
- "langium-cli": "^2.0.0",
59
+ "langium-cli": "^2.0.1",
60
60
  "npm-run-all": "^4.1.5",
61
- "typescript": "^5.1.6",
62
- "vitest": "^0.34.1"
63
- }
61
+ "typescript": "^5.2.2",
62
+ "vitest": "^0.34.3"
63
+ },
64
+ "packageManager": "yarn@3.6.3"
64
65
  }