@likec4/language-server 0.34.0 → 0.36.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';
@@ -269,8 +269,9 @@ export declare function isSpecificationElementKind(item: unknown): item is Speci
269
269
  export interface SpecificationRule extends AstNode {
270
270
  readonly $container: LikeC4Document;
271
271
  readonly $type: 'SpecificationRule';
272
+ elements: Array<SpecificationElementKind>;
272
273
  name: 'specification';
273
- specs: Array<SpecificationElementKind | SpecificationTag>;
274
+ tags: Array<SpecificationTag>;
274
275
  }
275
276
  export declare const SpecificationRule = "SpecificationRule";
276
277
  export declare function isSpecificationRule(item: unknown): item is SpecificationRule;
@@ -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';
@@ -352,7 +352,8 @@ export class LikeC4AstReflection extends AbstractAstReflection {
352
352
  return {
353
353
  name: 'SpecificationRule',
354
354
  mandatory: [
355
- { name: 'specs', type: 'array' }
355
+ { name: 'elements', type: 'array' },
356
+ { name: 'tags', type: 'array' }
356
357
  ]
357
358
  };
358
359
  }
@@ -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';
@@ -130,28 +130,33 @@ export const LikeC4Grammar = () => loadedLikeC4Grammar ?? (loadedLikeC4Grammar =
130
130
  "arguments": []
131
131
  },
132
132
  {
133
- "$type": "Assignment",
134
- "feature": "specs",
135
- "operator": "+=",
136
- "terminal": {
137
- "$type": "Alternatives",
138
- "elements": [
139
- {
133
+ "$type": "Alternatives",
134
+ "elements": [
135
+ {
136
+ "$type": "Assignment",
137
+ "feature": "elements",
138
+ "operator": "+=",
139
+ "terminal": {
140
140
  "$type": "RuleCall",
141
141
  "rule": {
142
142
  "$ref": "#/rules@4"
143
143
  },
144
144
  "arguments": []
145
- },
146
- {
145
+ }
146
+ },
147
+ {
148
+ "$type": "Assignment",
149
+ "feature": "tags",
150
+ "operator": "+=",
151
+ "terminal": {
147
152
  "$type": "RuleCall",
148
153
  "rule": {
149
154
  "$ref": "#/rules@5"
150
155
  },
151
156
  "arguments": []
152
157
  }
153
- ]
154
- },
158
+ }
159
+ ],
155
160
  "cardinality": "*"
156
161
  },
157
162
  {
@@ -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';
package/dist/logger.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { normalizeError, serializeError } from '@likec4/core';
2
+ import { normalizeError } from '@likec4/core';
3
3
  /* eslint-disable @typescript-eslint/no-redundant-type-constituents */
4
4
  let isSilent = false;
5
5
  export const logger = {
@@ -44,6 +44,7 @@ export function logWarnError(err) {
44
44
  logger.warn(err);
45
45
  return;
46
46
  }
47
- logger.warn(serializeError(err).message);
47
+ const error = normalizeError(err);
48
+ logger.warn(`${error.name}: ${error.message}`);
48
49
  }
49
50
  //# sourceMappingURL=logger.js.map
@@ -1,5 +1,5 @@
1
1
  import { findNodeForProperty } from 'langium';
2
- import { compact, isEmpty, map, pipe } from 'remeda';
2
+ import { compact, concat, isEmpty, map, pipe } from 'remeda';
3
3
  import { SymbolKind } from 'vscode-languageserver-protocol';
4
4
  import { ast } from '../ast';
5
5
  import { logError } from '../logger';
@@ -68,7 +68,7 @@ export class LikeC4DocumentSymbolProvider {
68
68
  const specKeywordNode = findNodeForProperty(cstModel, 'name');
69
69
  if (!specKeywordNode)
70
70
  return [];
71
- const specSymbols = pipe(astSpec.specs, map(nd => {
71
+ const specSymbols = pipe(concat(astSpec.elements, astSpec.tags), map(nd => {
72
72
  if (ast.isSpecificationElementKind(nd)) {
73
73
  return getElementKindSymbol(nd);
74
74
  }
@@ -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
  };
@@ -46,9 +46,13 @@ export class LikeC4ModelParser {
46
46
  }
47
47
  async parseDocument(doc, cancelToken) {
48
48
  const { elements, relations, views, specification } = cleanParsedModel(doc);
49
- const specs = doc.parseResult.value.specification?.specs.filter(ast.isSpecificationElementKind);
49
+ const specs = doc.parseResult.value.specification?.elements;
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,11 +1,8 @@
1
1
  import { DefaultScopeComputation, MultiMap, type AstNodeDescription, type PrecomputedScopes } from 'langium';
2
2
  import type { CancellationToken } from 'vscode-languageserver';
3
3
  import { ast, type LikeC4LangiumDocument } from '../ast';
4
- import type { LikeC4Services } from '../module';
5
4
  type ElementsContainer = ast.Model | ast.ElementBody | ast.ExtendElementBody;
6
5
  export declare class LikeC4ScopeComputation extends DefaultScopeComputation {
7
- private services;
8
- constructor(services: LikeC4Services);
9
6
  computeExports(document: LikeC4LangiumDocument, _cancelToken: CancellationToken): Promise<AstNodeDescription[]>;
10
7
  computeLocalScopes(document: LikeC4LangiumDocument, _cancelToken: CancellationToken): Promise<PrecomputedScopes>;
11
8
  protected processContainer(container: ElementsContainer, scopes: PrecomputedScopes, document: LikeC4LangiumDocument): MultiMap<string, AstNodeDescription>;
@@ -1,25 +1,20 @@
1
1
  import { DefaultScopeComputation, MultiMap } from 'langium';
2
- import { ast } from '../ast';
3
2
  import { isEmpty } from 'remeda';
3
+ import { ast } from '../ast';
4
4
  export class LikeC4ScopeComputation extends DefaultScopeComputation {
5
- services;
6
- constructor(services) {
7
- super(services);
8
- this.services = services;
9
- }
10
5
  computeExports(document, _cancelToken) {
11
6
  const { specification, model, views } = document.parseResult.value;
12
7
  const docExports = [];
13
- if (specification && specification.specs.length > 0) {
14
- for (const spec of specification.specs) {
15
- if (ast.isSpecificationElementKind(spec) && spec.kind && !isEmpty(spec.kind.name)) {
8
+ if (specification) {
9
+ for (const spec of specification.elements) {
10
+ if (spec.kind && !isEmpty(spec.kind.name)) {
16
11
  docExports.push(this.descriptions.createDescription(spec.kind, spec.kind.name, document));
17
- continue;
18
12
  }
19
- if (ast.isSpecificationTag(spec) && spec.tag && !isEmpty(spec.tag.name)) {
13
+ }
14
+ for (const spec of specification.tags) {
15
+ if (spec.tag && !isEmpty(spec.tag.name)) {
20
16
  docExports.push(this.descriptions.createDescription(spec.tag, spec.tag.name, document));
21
17
  docExports.push(this.descriptions.createDescription(spec.tag, '#' + spec.tag.name, document));
22
- continue;
23
18
  }
24
19
  }
25
20
  }
@@ -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.36.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.36.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
  }