@likec4/language-server 0.28.3 → 0.30.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.
@@ -116,7 +116,11 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
116
116
  });
117
117
  return;
118
118
  }
119
- if (ast.isAnyStringProperty(node)) {
119
+ // ViewProperty | ElementStringProperty | RelationStringProperty | LinkProperty
120
+ if (ast.isViewProperty(node) ||
121
+ ast.isElementStringProperty(node) ||
122
+ ast.isRelationStringProperty(node) ||
123
+ ast.isLinkProperty(node)) {
120
124
  acceptor({
121
125
  node,
122
126
  property: 'key',
@@ -1,11 +1,10 @@
1
- import { failExpectedNever } from '@likec4/core';
2
- import { Fqn } from '@likec4/core/types';
1
+ import { failExpectedNever, AsFqn } from '@likec4/core';
3
2
  import { MultiMap } from 'langium';
4
3
  import { isEmpty, isNil } from 'remeda';
5
4
  import { ElementOps, ast } from '../ast';
6
5
  import { strictElementRefFqn } from '../elementRef';
7
6
  export function computeDocumentFqn(document, services) {
8
- const c4fqns = document.c4fqns = new MultiMap();
7
+ const c4fqns = (document.c4fqns = new MultiMap());
9
8
  const { model } = document.parseResult.value;
10
9
  if (!model?.elements) {
11
10
  return;
@@ -26,7 +25,7 @@ export function computeDocumentFqn(document, services) {
26
25
  continue;
27
26
  }
28
27
  if (ast.isElement(el)) {
29
- const fqn = Fqn(el.name, parent);
28
+ const fqn = AsFqn(el.name, parent);
30
29
  const path = locator.getAstNodePath(el);
31
30
  c4fqns.add(fqn, path);
32
31
  ElementOps.writeId(el, fqn);
@@ -1,6 +1,5 @@
1
- import type { Fqn } from '@likec4/core/types';
2
- import type { LangiumDocument, LangiumDocuments } from 'langium';
3
- import { StreamImpl } from 'langium';
1
+ import type { Fqn } from '@likec4/core';
2
+ import type { LangiumDocument, LangiumDocuments, Stream } from 'langium';
4
3
  import type { ast } from '../ast';
5
4
  import { type LikeC4LangiumDocument } from '../ast';
6
5
  import type { LikeC4Services } from '../module';
@@ -15,18 +14,20 @@ export interface FqnIndexEntry {
15
14
  path: string;
16
15
  }
17
16
  export declare class FqnIndex {
18
- private services;
19
17
  protected langiumDocuments: LangiumDocuments;
20
18
  constructor(services: LikeC4Services);
21
19
  private documents;
22
20
  private entries;
23
- get(el: ast.Element): Fqn | null;
24
- byFqn(fqn: Fqn): import("langium").Stream<{
21
+ getFqn(el: ast.Element): Fqn | null;
22
+ byFqn(fqn: Fqn): Stream<{
25
23
  path: string;
26
- doc: FqnIndexedDocument;
24
+ doc: LikeC4LangiumDocument;
27
25
  }>;
28
- directChildrenOf(parent: Fqn): StreamImpl<IterableIterator<FqnIndexEntry> | null, FqnIndexEntry>;
29
- uniqueDescedants(parent: Fqn): StreamImpl<IterableIterator<FqnIndexEntry> | null, FqnIndexEntry>;
26
+ directChildrenOf(parent: Fqn): Stream<FqnIndexEntry>;
27
+ /**
28
+ * Returns descedant elements with unique names in the scope
29
+ */
30
+ uniqueDescedants(parent: Fqn): Stream<FqnIndexEntry>;
30
31
  }
31
32
  export {};
32
33
  //# sourceMappingURL=fqn-index.d.ts.map
@@ -1,17 +1,15 @@
1
- import { nameFromFqn, parentFqn } from '@likec4/core/utils';
1
+ import { nameFromFqn, parentFqn } from '@likec4/core';
2
2
  import { DONE_RESULT, DocumentState, MultiMap, StreamImpl } from 'langium';
3
3
  import { isNil } from 'remeda';
4
4
  import { ElementOps, isLikeC4LangiumDocument } from '../ast';
5
5
  import { logger } from '../logger';
6
6
  import { computeDocumentFqn } from './fqn-computation';
7
7
  export function isFqnIndexedDocument(doc) {
8
- return isLikeC4LangiumDocument(doc) && doc.state >= DocumentState.IndexedContent && !isNil(doc.c4fqns);
8
+ return (isLikeC4LangiumDocument(doc) && doc.state >= DocumentState.IndexedContent && !isNil(doc.c4fqns));
9
9
  }
10
10
  export class FqnIndex {
11
- services;
12
11
  langiumDocuments;
13
12
  constructor(services) {
14
- this.services = services;
15
13
  this.langiumDocuments = services.shared.workspace.LangiumDocuments;
16
14
  services.shared.workspace.DocumentBuilder.onBuildPhase(DocumentState.IndexedContent, (docs, _cancelToken) => {
17
15
  for (const doc of docs) {
@@ -32,7 +30,7 @@ export class FqnIndex {
32
30
  entries() {
33
31
  return this.documents().flatMap(doc => doc.c4fqns.entries().map(([fqn, path]) => ({ fqn, path, doc })));
34
32
  }
35
- get(el) {
33
+ getFqn(el) {
36
34
  return ElementOps.readId(el) ?? null;
37
35
  // if (fqn) {
38
36
  // const doc = getDocument(el)
@@ -75,6 +73,9 @@ export class FqnIndex {
75
73
  return DONE_RESULT;
76
74
  });
77
75
  }
76
+ /**
77
+ * Returns descedant elements with unique names in the scope
78
+ */
78
79
  uniqueDescedants(parent) {
79
80
  return new StreamImpl(() => {
80
81
  const prefix = `${parent}.`;
@@ -86,6 +87,7 @@ export class FqnIndex {
86
87
  .forEach(e => {
87
88
  const name = nameFromFqn(e.fqn);
88
89
  const entry = { ...e, name };
90
+ // To keep direct children always
89
91
  if (parentFqn(e.fqn) === parent) {
90
92
  childrenNames.add(name);
91
93
  nested.add(name, entry);
@@ -5,10 +5,12 @@ export declare class LikeC4ModelBuilder {
5
5
  private services;
6
6
  private fqnIndex;
7
7
  private langiumDocuments;
8
+ private readonly cachedModel;
8
9
  constructor(services: LikeC4Services);
9
- private get connection();
10
+ private cleanCache;
10
11
  private documents;
11
- buildModel(): c4.LikeC4Model | undefined;
12
+ buildModel(): c4.LikeC4Model | null;
13
+ private _buildModel;
12
14
  /**
13
15
  * @returns if the document was changed
14
16
  */
@@ -1,4 +1,4 @@
1
- import { ModelIndex, assignNavigateTo, computeView } from '@likec4/core';
1
+ import { ModelIndex, assignNavigateTo, computeView, invariant } from '@likec4/core';
2
2
  import { DefaultElementShape, DefaultThemeColor } from '@likec4/core/types';
3
3
  import { compareByFqnHierarchically, parentFqn } from '@likec4/core/utils';
4
4
  import { DocumentState, getDocument } from 'langium';
@@ -6,7 +6,6 @@ import objectHash from 'object-hash';
6
6
  import { clone } from 'rambdax';
7
7
  import * as R from 'remeda';
8
8
  import stripIndent from 'strip-indent';
9
- import invariant from 'tiny-invariant';
10
9
  import { ElementViewOps, ast, cleanParsedModel, isLikeC4LangiumDocument, isValidLikeC4LangiumDocument, resolveRelationPoints, streamModel, toAutoLayout, toElementStyle } from '../ast';
11
10
  import { elementRef, strictElementRefFqn } from '../elementRef';
12
11
  import { logger } from '../logger';
@@ -16,6 +15,7 @@ export class LikeC4ModelBuilder {
16
15
  services;
17
16
  fqnIndex;
18
17
  langiumDocuments;
18
+ cachedModel = {};
19
19
  constructor(services) {
20
20
  this.services = services;
21
21
  this.fqnIndex = services.likec4.FqnIndex;
@@ -23,9 +23,6 @@ export class LikeC4ModelBuilder {
23
23
  services.shared.workspace.DocumentBuilder.onBuildPhase(DocumentState.Validated, (docs, cancelToken) => {
24
24
  let countOfChangedDocs = 0;
25
25
  for (const doc of docs) {
26
- if (cancelToken.isCancellationRequested) {
27
- break;
28
- }
29
26
  if (!isLikeC4LangiumDocument(doc)) {
30
27
  continue;
31
28
  }
@@ -38,22 +35,31 @@ export class LikeC4ModelBuilder {
38
35
  logger.error(e);
39
36
  }
40
37
  }
41
- if (countOfChangedDocs > 0 && !cancelToken.isCancellationRequested) {
42
- this.notifyClient();
38
+ if (countOfChangedDocs > 0) {
39
+ this.cleanCache();
40
+ this.notifyClient(cancelToken);
43
41
  }
44
42
  });
45
43
  }
46
- get connection() {
47
- return this.services.shared.lsp.Connection;
44
+ cleanCache() {
45
+ delete this.cachedModel.last;
48
46
  }
49
47
  documents() {
50
48
  return this.langiumDocuments.all.filter(isValidLikeC4LangiumDocument).toArray();
51
49
  }
52
50
  buildModel() {
51
+ if ('last' in this.cachedModel) {
52
+ logger.debug('returning cached model');
53
+ return this.cachedModel.last;
54
+ }
55
+ return (this.cachedModel.last = this._buildModel());
56
+ }
57
+ _buildModel() {
58
+ logger.debug('_buildModel');
53
59
  const docs = this.documents();
54
60
  if (docs.length === 0) {
55
61
  logger.debug('No documents to build model from');
56
- return;
62
+ return null;
57
63
  }
58
64
  // TODO:
59
65
  try {
@@ -61,27 +67,32 @@ export class LikeC4ModelBuilder {
61
67
  kinds: {}
62
68
  };
63
69
  R.forEach(R.map(docs, R.prop('c4Specification')), spec => Object.assign(c4Specification.kinds, spec.kinds));
64
- const toModelElement = (el) => {
65
- const kind = c4Specification.kinds[el.kind];
70
+ const toModelElement = ({ astPath, ...parsed }) => {
71
+ const kind = c4Specification.kinds[parsed.kind];
66
72
  if (kind) {
67
- const { astPath, ...model } = el;
68
73
  return {
69
- ...(kind.shape !== DefaultElementShape ? { shape: kind.shape } : {}),
70
- ...(kind.color !== DefaultThemeColor ? { color: kind.color } : {}),
71
- ...model
74
+ shape: kind.shape,
75
+ color: kind.color,
76
+ description: null,
77
+ technology: null,
78
+ tags: [],
79
+ ...parsed
72
80
  };
73
81
  }
74
82
  return null;
75
83
  };
76
84
  const elements = R.pipe(R.flatMap(docs, d => d.c4Elements), R.map(toModelElement), R.compact, R.sort(compareByFqnHierarchically), R.reduce((acc, el) => {
77
85
  const parent = parentFqn(el.id);
78
- if (!parent || parent in acc) {
79
- if (el.id in acc) {
80
- logger.warn(`Duplicate element id: ${el.id}`);
81
- return acc;
82
- }
83
- acc[el.id] = el;
86
+ if (parent && R.isNil(acc[parent])) {
87
+ logger.warn(`No parent found for ${el.id}`);
88
+ return acc;
89
+ }
90
+ if (el.id in acc) {
91
+ // should not happen, as validated
92
+ logger.warn(`Duplicate element id: ${el.id}`);
93
+ return acc;
84
94
  }
95
+ acc[el.id] = el;
85
96
  return acc;
86
97
  }, {}));
87
98
  const toModelRelation = ({ astPath, source, target, ...model }) => {
@@ -121,7 +132,7 @@ export class LikeC4ModelBuilder {
121
132
  }
122
133
  catch (e) {
123
134
  logger.error(e);
124
- return;
135
+ return null;
125
136
  }
126
137
  }
127
138
  /**
@@ -187,7 +198,7 @@ export class LikeC4ModelBuilder {
187
198
  invariant(astNode.kind.ref, 'Element kind is not resolved: ' + astNode.name);
188
199
  const kind = astNode.kind.ref.name;
189
200
  const tags = (astNode.body && this.convertTags(astNode.body)) ?? [];
190
- const styleProps = astNode.body?.props.find(ast.isElementStyleProperty)?.props;
201
+ const styleProps = astNode.body?.props.find(ast.isElementStyleProperties)?.props;
191
202
  const { color, shape } = toElementStyle(styleProps);
192
203
  const astPath = this.getAstNodePath(astNode);
193
204
  let [title, description, technology] = astNode.props;
@@ -217,7 +228,7 @@ export class LikeC4ModelBuilder {
217
228
  target
218
229
  };
219
230
  const id = objectHash(hashdata);
220
- const title = astNode.title ?? astNode.definition?.props.find(p => p.key === 'title')?.value ?? '';
231
+ const title = astNode.title ?? astNode.body?.props.find(p => p.key === 'title')?.value ?? '';
221
232
  return {
222
233
  id,
223
234
  ...hashdata,
@@ -333,7 +344,7 @@ export class LikeC4ModelBuilder {
333
344
  if (ast.isExtendElement(node)) {
334
345
  return strictElementRefFqn(node.element);
335
346
  }
336
- const fqn = this.fqnIndex.get(node);
347
+ const fqn = this.fqnIndex.getFqn(node);
337
348
  invariant(fqn, `Not indexed element: ${this.getAstNodePath(node)}`);
338
349
  return fqn;
339
350
  }
@@ -344,8 +355,8 @@ export class LikeC4ModelBuilder {
344
355
  return el.tags?.value.map(tagRef => tagRef.ref?.name) ?? [];
345
356
  }
346
357
  scheduledCb = null;
347
- notifyClient() {
348
- const connection = this.connection;
358
+ notifyClient(cancelToken) {
359
+ const connection = this.services.shared.lsp.Connection;
349
360
  if (!connection) {
350
361
  return;
351
362
  }
@@ -356,8 +367,10 @@ export class LikeC4ModelBuilder {
356
367
  this.scheduledCb = setTimeout(() => {
357
368
  logger.debug('send onDidChangeModel');
358
369
  this.scheduledCb = null;
359
- void connection.sendNotification(Rpc.onDidChangeModel, '');
360
- }, 350);
370
+ if (!cancelToken.isCancellationRequested) {
371
+ void connection.sendNotification(Rpc.onDidChangeModel, '');
372
+ }
373
+ }, 300);
361
374
  }
362
375
  }
363
376
  //# sourceMappingURL=model-builder.js.map
@@ -17,18 +17,15 @@ export class LikeC4ScopeProvider extends DefaultScopeProvider {
17
17
  this.fqnIndex = services.likec4.FqnIndex;
18
18
  }
19
19
  directChildrenOf(parent) {
20
- return this.fqnIndex
21
- .directChildrenOf(parent)
22
- .map(toAstNodeDescription);
20
+ return this.fqnIndex.directChildrenOf(parent).map(toAstNodeDescription);
23
21
  }
22
+ // we need lazy resolving here
24
23
  uniqueDescedants(of) {
25
24
  return new StreamImpl(() => {
26
25
  const element = of();
27
- const fqn = element && this.fqnIndex.get(element);
26
+ const fqn = element && this.fqnIndex.getFqn(element);
28
27
  if (fqn) {
29
- return this.fqnIndex.uniqueDescedants(fqn)
30
- .map(toAstNodeDescription)
31
- .iterator();
28
+ return this.fqnIndex.uniqueDescedants(fqn).map(toAstNodeDescription).iterator();
32
29
  }
33
30
  return null;
34
31
  }, iterator => {
@@ -57,21 +54,21 @@ export class LikeC4ScopeProvider extends DefaultScopeProvider {
57
54
  getScope(context) {
58
55
  const referenceType = this.reflection.getReferenceType(context);
59
56
  try {
60
- const node = context.container;
57
+ const container = context.container;
61
58
  // const path = this.services.workspace.AstNodeLocator.getAstNodePath(node)
62
59
  if (referenceType === ast.Element) {
63
- if (ast.isStrictElementRef(node)) {
64
- if (isElementRefHead(node)) {
60
+ if (ast.isStrictElementRef(container)) {
61
+ if (isElementRefHead(container)) {
65
62
  return this.getGlobalScope(referenceType);
66
63
  }
67
- const parent = parentStrictElementRef(node);
64
+ const parent = parentStrictElementRef(container);
68
65
  return new StreamScope(this.directChildrenOf(parent));
69
66
  }
70
- if (ast.isElementRef(node) && !isElementRefHead(node)) {
71
- return new StreamScope(this.scopeElementRef(node));
67
+ if (ast.isElementRef(container) && !isElementRefHead(container)) {
68
+ return new StreamScope(this.scopeElementRef(container));
72
69
  }
73
70
  }
74
- return this.computeScope(node, referenceType);
71
+ return this.computeScope(container, referenceType);
75
72
  }
76
73
  catch (e) {
77
74
  // console.error(e)
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  import type { ValidationCheck } from 'langium';
3
2
  import type { ast } from '../ast';
4
3
  import type { LikeC4Services } from '../module';
@@ -1,9 +1,9 @@
1
1
  export const elementChecks = (services) => {
2
2
  const fqnIndex = services.likec4.FqnIndex;
3
3
  return (el, accept) => {
4
- const fqn = fqnIndex.get(el);
4
+ const fqn = fqnIndex.getFqn(el);
5
5
  if (!fqn) {
6
- accept('error', 'Not indexed', {
6
+ accept('error', 'Not indexed element', {
7
7
  node: el,
8
8
  property: 'name'
9
9
  });
@@ -11,12 +11,6 @@ export const elementChecks = (services) => {
11
11
  }
12
12
  const withSameFqn = fqnIndex.byFqn(fqn).limit(2).count();
13
13
  if (withSameFqn > 1) {
14
- // console.error(withSameFqn.map(e => ({
15
- // fqn,
16
- // name: el.name,
17
- // path: e.path,
18
- // doc: e.doc.uri.toString()
19
- // })))
20
14
  accept('error', `Duplicate element name ${el.name !== fqn ? el.name + ' (' + fqn + ')' : el.name}`, {
21
15
  node: el,
22
16
  property: 'name'
@@ -1,31 +1,36 @@
1
- import { resolveRelationPoints } from '../ast';
1
+ import { BaseError } from '@likec4/core';
2
2
  import { isSameHierarchy } from '@likec4/core/utils';
3
+ import { resolveRelationPoints } from '../ast';
3
4
  export const relationChecks = (services) => {
4
5
  const fqnIndex = services.likec4.FqnIndex;
5
6
  return (el, accept) => {
6
7
  try {
7
8
  const coupling = resolveRelationPoints(el);
8
- const target = fqnIndex.get(coupling.target);
9
- if (!target) {
10
- return accept('error', 'Invalid target', {
11
- node: el,
12
- property: 'target'
13
- });
14
- }
15
- const source = fqnIndex.get(coupling.source);
16
- if (!source) {
17
- return accept('error', 'Invalid source', {
18
- node: el
19
- });
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', {
14
+ node: el,
15
+ property: 'target'
16
+ });
17
+ }
18
+ if (!source) {
19
+ accept('error', 'Source not found', {
20
+ node: el,
21
+ property: 'source'
22
+ });
23
+ }
24
+ return;
20
25
  }
21
26
  if (isSameHierarchy(source, target)) {
22
- return accept('error', 'Invalid relation (same hierarchy)', {
27
+ return accept('error', 'Invalid parent-child relation', {
23
28
  node: el
24
29
  });
25
30
  }
26
31
  }
27
32
  catch (e) {
28
- if (e instanceof Error) {
33
+ if (e instanceof BaseError) {
29
34
  return accept('error', e.message, {
30
35
  node: el
31
36
  });
@@ -34,21 +39,6 @@ export const relationChecks = (services) => {
34
39
  node: el
35
40
  });
36
41
  }
37
- // const fqn = fqnIndex.get(el)
38
- // if (!fqn) {
39
- // accept('error', 'Not indexed', {
40
- // node: el,
41
- // property: 'name',
42
- // })
43
- // return
44
- // }
45
- // const withSameFqn = fqnIndex.byFqn(fqn)
46
- // if (withSameFqn.length > 1) {
47
- // accept('error', `Duplicate element name ${el.name !== fqn ? el.name +' (' + fqn + ')' : el.name}`, {
48
- // node: el,
49
- // property: 'name',
50
- // })
51
- // }
52
42
  };
53
43
  };
54
44
  //# sourceMappingURL=relation.js.map
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.28.3",
4
+ "version": "0.30.0",
5
5
  "license": "MIT",
6
6
  "bugs": "https://github.com/likec4/likec4/issues",
7
7
  "homepage": "https://likec4.dev",
@@ -46,6 +46,7 @@
46
46
  },
47
47
  "scripts": {
48
48
  "turbo-build": "run -T turbo run build --filter='language-server'",
49
+ "turbo-compile": "run -T turbo run compile --filter='language-server'",
49
50
  "compile": "tsc --noEmit",
50
51
  "watch:langium": "langium generate --watch",
51
52
  "watch:ts": "tsc --watch",
@@ -58,14 +59,13 @@
58
59
  "test:watch": "run -T vitest"
59
60
  },
60
61
  "dependencies": {
61
- "@likec4/core": "0.28.3",
62
+ "@likec4/core": "0.30.0",
62
63
  "langium": "^1.2.1",
63
64
  "nanoid": "^4.0.2",
64
65
  "object-hash": "^3.0.0",
65
66
  "rambdax": "^9.1.1",
66
67
  "remeda": "^1.23.0",
67
68
  "strip-indent": "^4.0.0",
68
- "tiny-invariant": "^1.3.1",
69
69
  "vscode-languageserver": "~8.1.0",
70
70
  "vscode-languageserver-protocol": "~3.17.3"
71
71
  },