@likec4/language-server 0.4.0 → 0.5.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
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import * as ast from './generated/ast';
2
3
  import type { LangiumDocument } from 'langium';
3
4
  import type { LikeC4Document } from './generated/ast';
package/dist/ast.js CHANGED
@@ -11,7 +11,7 @@ export function c4hash({ c4Specification, c4Elements, c4Relations, c4Views }) {
11
11
  c4Relations,
12
12
  c4Views
13
13
  }, {
14
- respectType: false,
14
+ respectType: false
15
15
  });
16
16
  }
17
17
  const idattr = Symbol.for('idattr');
@@ -39,9 +39,9 @@ export function cleanParsedModel(doc) {
39
39
  doc.c4Specification = {
40
40
  kinds: {}
41
41
  };
42
- const elements = doc.c4Elements = [];
43
- const relations = doc.c4Relations = [];
44
- const views = doc.c4Views = [];
42
+ const elements = (doc.c4Elements = []);
43
+ const relations = (doc.c4Relations = []);
44
+ const views = (doc.c4Views = []);
45
45
  return {
46
46
  elements,
47
47
  relations,
@@ -53,21 +53,22 @@ export function isLikeC4LangiumDocument(doc) {
53
53
  return doc.textDocument.languageId === LikeC4LanguageMetaData.languageId;
54
54
  }
55
55
  export function isParsedLikeC4LangiumDocument(doc) {
56
- return isLikeC4LangiumDocument(doc) && ['c4Specification', 'c4Elements', 'c4Relations', 'c4Views'].every(key => key in doc);
56
+ return (isLikeC4LangiumDocument(doc) &&
57
+ ['c4Specification', 'c4Elements', 'c4Relations', 'c4Views'].every(key => key in doc));
57
58
  }
58
59
  export const isValidDocument = (doc) => {
59
60
  if (!isLikeC4LangiumDocument(doc))
60
61
  return false;
61
62
  const { state, parseResult, diagnostics } = doc;
62
- return (state === DocumentState.Validated
63
- && parseResult.lexerErrors.length === 0
64
- && (!diagnostics || diagnostics.every(d => d.severity !== 1)));
63
+ return (state === DocumentState.Validated &&
64
+ parseResult.lexerErrors.length === 0 &&
65
+ (!diagnostics || diagnostics.every(d => d.severity !== 1)));
65
66
  };
66
67
  export function* streamModel(doc) {
67
68
  const elements = doc.parseResult.value.model?.elements ?? [];
68
69
  const traverseStack = [...elements];
69
70
  let el;
70
- while (el = traverseStack.shift()) {
71
+ while ((el = traverseStack.shift())) {
71
72
  if (ast.isExtendElement(el)) {
72
73
  traverseStack.push(...el.body.elements);
73
74
  continue;
@@ -0,0 +1,4 @@
1
+ export declare const specification: {
2
+ uri: string;
3
+ document: string;
4
+ };
@@ -0,0 +1,8 @@
1
+ export const specification = {
2
+ uri: 'builtin:///specification.likec4',
3
+ document: `
4
+ specification {
5
+ element element
6
+ }
7
+ `.trimStart()
8
+ };
@@ -16,7 +16,7 @@ export type ElementShape = 'browser' | 'cylinder' | 'person' | 'queue' | 'rectan
16
16
  export type Expression = ElementExpression | InOutExpression | IncomingExpression | OutgoingExpression | RelationExpression;
17
17
  export declare const Expression = "Expression";
18
18
  export declare function isExpression(item: unknown): item is Expression;
19
- export type Name = 'model' | ElementShape | ThemeColor | string;
19
+ export type Name = 'element' | 'model' | ElementShape | ThemeColor | string;
20
20
  export type ThemeColor = 'muted' | 'primary' | 'secondary';
21
21
  export type View = ElementView;
22
22
  export declare const View = "View";
@@ -535,6 +535,10 @@ export const LikeC4Grammar = () => loadedLikeC4Grammar ?? (loadedLikeC4Grammar =
535
535
  },
536
536
  "arguments": []
537
537
  },
538
+ {
539
+ "$type": "Keyword",
540
+ "value": "element"
541
+ },
538
542
  {
539
543
  "$type": "Keyword",
540
544
  "value": "model"
@@ -3,6 +3,7 @@
3
3
  * This program and the accompanying materials are made available under the
4
4
  * terms of the MIT License, which is available in the project root.
5
5
  ******************************************************************************/
6
+ /// <reference types="react" />
6
7
  import { type DocumentSymbolProvider, type MaybePromise } from 'langium';
7
8
  import { type DocumentSymbol } from 'vscode-languageserver-protocol';
8
9
  import { type LikeC4LangiumDocument, ast } from '../ast';
@@ -56,13 +56,15 @@ export class LikeC4DocumentSymbolProvider {
56
56
  }
57
57
  if (specSymbols.length === 0)
58
58
  return [];
59
- return [{
59
+ return [
60
+ {
60
61
  kind: SymbolKind.Class,
61
62
  name: astSpec.name,
62
63
  range: cstModel.range,
63
64
  selectionRange: specKeywordNode.range,
64
65
  children: specSymbols
65
- }];
66
+ }
67
+ ];
66
68
  };
67
69
  getModelSymbols = (astModel) => {
68
70
  const cstModel = astModel?.$cstNode;
@@ -71,13 +73,15 @@ export class LikeC4DocumentSymbolProvider {
71
73
  const nameNode = findNodeForProperty(cstModel, 'name');
72
74
  if (!nameNode)
73
75
  return [];
74
- return [{
76
+ return [
77
+ {
75
78
  kind: SymbolKind.Class,
76
79
  name: astModel.name,
77
80
  range: cstModel.range,
78
81
  selectionRange: nameNode.range,
79
82
  children: astModel.elements.flatMap(this.getElementsSymbol)
80
- }];
83
+ }
84
+ ];
81
85
  };
82
86
  getElementsSymbol = (el) => {
83
87
  if (ast.isExtendElement(el)) {
@@ -93,13 +97,15 @@ export class LikeC4DocumentSymbolProvider {
93
97
  const nameNode = astElement.element.$cstNode;
94
98
  if (!cst || !nameNode)
95
99
  return [];
96
- return [{
100
+ return [
101
+ {
97
102
  kind: SymbolKind.Constructor,
98
103
  name: nameNode.text,
99
104
  range: cst.range,
100
105
  selectionRange: nameNode.range,
101
106
  children: astElement.body.elements.flatMap(this.getElementsSymbol)
102
- }];
107
+ }
108
+ ];
103
109
  };
104
110
  getElementSymbol = (astElement) => {
105
111
  const cst = astElement.$cstNode;
@@ -112,14 +118,16 @@ export class LikeC4DocumentSymbolProvider {
112
118
  const kind = astElement.kind.$refText;
113
119
  // TODO: return the title as well
114
120
  const detail = kind; // + (astElement.title ? ': ' + astElement.title : '').replaceAll('\n', ' ').trim()
115
- return [{
121
+ return [
122
+ {
116
123
  kind: SymbolKind.Constructor,
117
124
  name: name,
118
125
  range: cst.range,
119
126
  selectionRange: nameNode.range,
120
127
  detail,
121
128
  children: astElement.body?.elements.flatMap(this.getElementsSymbol) ?? []
122
- }];
129
+ }
130
+ ];
123
131
  };
124
132
  getModelViewsSymbols = (astViews) => {
125
133
  const cst = astViews?.$cstNode;
@@ -128,12 +136,14 @@ export class LikeC4DocumentSymbolProvider {
128
136
  const nameNode = findNodeForProperty(cst, 'name');
129
137
  if (!nameNode)
130
138
  return [];
131
- return [{
139
+ return [
140
+ {
132
141
  kind: SymbolKind.Class,
133
142
  name: astViews.name,
134
143
  range: cst.range,
135
144
  selectionRange: nameNode.range,
136
145
  children: []
137
- }];
146
+ }
147
+ ];
138
148
  };
139
149
  }
@@ -35,10 +35,7 @@ export class LikeC4HoverProvider extends AstNodeHoverProvider {
35
35
  if (!el) {
36
36
  return;
37
37
  }
38
- const lines = [
39
- el.id,
40
- `${el.kind}: **${el.title}**`
41
- ];
38
+ const lines = [el.id, `${el.kind}: **${el.title}**`];
42
39
  if (el.tags && el.tags.length > 0) {
43
40
  lines.push(' \n', el.tags.map(t => '#' + t).join(', '));
44
41
  }
@@ -7,13 +7,13 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
7
7
  const keyword = (keyword, _index) => acceptor({
8
8
  node,
9
9
  keyword,
10
- type: SemanticTokenTypes.keyword,
10
+ type: SemanticTokenTypes.keyword
11
11
  });
12
12
  if (ast.isElementRef(node) || ast.isStrictElementRef(node)) {
13
13
  acceptor({
14
14
  node,
15
15
  property: 'el',
16
- type: isElementRefHead(node) ? SemanticTokenTypes.variable : SemanticTokenTypes.property,
16
+ type: isElementRefHead(node) ? SemanticTokenTypes.variable : SemanticTokenTypes.property
17
17
  });
18
18
  // acceptor({
19
19
  // node,
@@ -34,7 +34,9 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
34
34
  });
35
35
  return;
36
36
  }
37
- if (ast.isRelationExpression(node) || ast.isIncomingExpression(node) || ast.isOutgoingExpression(node)) {
37
+ if (ast.isRelationExpression(node) ||
38
+ ast.isIncomingExpression(node) ||
39
+ ast.isOutgoingExpression(node)) {
38
40
  keyword('->');
39
41
  return;
40
42
  }
@@ -74,9 +76,7 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
74
76
  node,
75
77
  property: 'name',
76
78
  type: SemanticTokenTypes.type,
77
- modifier: [
78
- SemanticTokenModifiers.definition,
79
- ]
79
+ modifier: [SemanticTokenModifiers.definition]
80
80
  });
81
81
  return;
82
82
  }
@@ -126,7 +126,7 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
126
126
  acceptor({
127
127
  node,
128
128
  property: 'key',
129
- type: SemanticTokenTypes.keyword,
129
+ type: SemanticTokenTypes.keyword
130
130
  });
131
131
  acceptor({
132
132
  node,
@@ -139,7 +139,7 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
139
139
  acceptor({
140
140
  node,
141
141
  property: 'key',
142
- type: SemanticTokenTypes.keyword,
142
+ type: SemanticTokenTypes.keyword
143
143
  });
144
144
  if ('value' in node) {
145
145
  acceptor({
@@ -207,9 +207,7 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
207
207
  node,
208
208
  property: 'name',
209
209
  type: SemanticTokenTypes.variable,
210
- modifier: [
211
- SemanticTokenModifiers.declaration,
212
- ]
210
+ modifier: [SemanticTokenModifiers.declaration]
213
211
  });
214
212
  acceptor({
215
213
  node,
@@ -230,7 +228,7 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
230
228
  acceptor({
231
229
  node,
232
230
  property: 'name',
233
- type: SemanticTokenTypes.variable,
231
+ type: SemanticTokenTypes.variable
234
232
  });
235
233
  }
236
234
  if (node.viewOf) {
@@ -1,4 +1,3 @@
1
- export * from './CodeLensProvider';
2
1
  export * from './DocumentSymbolProvider';
3
2
  export * from './HoverProvider';
4
3
  export * from './SemanticTokenProvider';
package/dist/lsp/index.js CHANGED
@@ -1,4 +1,3 @@
1
- export * from './CodeLensProvider';
2
1
  export * from './DocumentSymbolProvider';
3
2
  export * from './HoverProvider';
4
3
  export * from './SemanticTokenProvider';
@@ -42,7 +42,7 @@ export class FqnIndex {
42
42
  directChildrenOf(parent) {
43
43
  return this.#index
44
44
  .entriesGroupedByKey()
45
- .flatMap(([fqn, descrs]) => (descrs.length === 1 && parentFqn(fqn) === parent) ? descrs : []);
45
+ .flatMap(([fqn, descrs]) => (descrs.length === 1 && parentFqn(fqn) === parent ? descrs : []));
46
46
  }
47
47
  uniqueDescedants(parent) {
48
48
  return new StreamImpl(() => {
@@ -61,7 +61,7 @@ export class FqnIndex {
61
61
  }
62
62
  }
63
63
  });
64
- if ((children.length + descedants.length) === 0) {
64
+ if (children.length + descedants.length === 0) {
65
65
  return null;
66
66
  }
67
67
  const nested = new MultiMap(children.map(([_fqn, desc]) => [desc.name, desc]));
@@ -72,9 +72,9 @@ export class FqnIndex {
72
72
  }
73
73
  return nested
74
74
  .entriesGroupedByKey()
75
- .flatMap(([_name, descrs]) => descrs.length === 1 ? descrs : [])
75
+ .flatMap(([_name, descrs]) => (descrs.length === 1 ? descrs : []))
76
76
  .iterator();
77
- }, (iterator) => {
77
+ }, iterator => {
78
78
  if (iterator) {
79
79
  return iterator.next();
80
80
  }
@@ -128,7 +128,9 @@ export class FqnIndex {
128
128
  }
129
129
  cleanIndexedElements(docUri) {
130
130
  const docUriAsString = docUri.toString();
131
- const toDelete = this.#index.entries().filter(([, indexed]) => indexed.documentUri.toString() === docUriAsString);
131
+ const toDelete = this.#index
132
+ .entries()
133
+ .filter(([, indexed]) => indexed.documentUri.toString() === docUriAsString);
132
134
  for (const [fqn, indexed] of toDelete.toArray()) {
133
135
  this.#index.delete(fqn, indexed);
134
136
  }
@@ -67,7 +67,7 @@ export class LikeC4ModelBuilder {
67
67
  return {
68
68
  ...(kind.shape !== DefaultElementShape ? { shape: kind.shape } : {}),
69
69
  ...(kind.color !== DefaultThemeColor ? { color: kind.color } : {}),
70
- ...(omit(['astPath'], el))
70
+ ...omit(['astPath'], el)
71
71
  };
72
72
  }
73
73
  return null;
@@ -127,7 +127,7 @@ export class LikeC4ModelBuilder {
127
127
  const styleProps = toElementStyle(style?.props);
128
128
  specification.kinds[kind.name] = {
129
129
  color: styleProps.color ?? DefaultThemeColor,
130
- shape: styleProps.shape ?? DefaultElementShape,
130
+ shape: styleProps.shape ?? DefaultElementShape
131
131
  };
132
132
  }
133
133
  catch (e) {
@@ -181,7 +181,7 @@ export class LikeC4ModelBuilder {
181
181
  const styleProps = astNode.body?.props.find(ast.isElementStyleProperty)?.props;
182
182
  const { color, shape } = toElementStyle(styleProps);
183
183
  const astPath = this.getAstNodePath(astNode);
184
- let [title, description, technology,] = astNode.props;
184
+ let [title, description, technology] = astNode.props;
185
185
  const bodyProps = astNode.body?.props.filter((p) => ast.isElementStringProperty(p)) ?? [];
186
186
  title = title ?? bodyProps.find(p => p.key === 'title')?.value;
187
187
  description = description ?? bodyProps.find(p => p.key === 'description')?.value;
@@ -195,7 +195,7 @@ export class LikeC4ModelBuilder {
195
195
  ...(description && { description }),
196
196
  ...(tags.length > 0 ? { tags } : {}),
197
197
  ...(shape && shape !== DefaultElementShape ? { shape } : {}),
198
- ...(color && color !== DefaultThemeColor ? { color } : {}),
198
+ ...(color && color !== DefaultThemeColor ? { color } : {})
199
199
  };
200
200
  }
201
201
  parseRelation(astNode) {
@@ -208,11 +208,11 @@ export class LikeC4ModelBuilder {
208
208
  target
209
209
  };
210
210
  const id = objectHash(hashdata);
211
- const title = astNode.definition?.props.find(p => p.key === 'title')?.value ?? '';
211
+ const title = astNode.title ?? astNode.definition?.props.find(p => p.key === 'title')?.value ?? '';
212
212
  return {
213
213
  id,
214
214
  ...hashdata,
215
- title: astNode.title ?? title
215
+ title
216
216
  };
217
217
  }
218
218
  parseElementExpression(astNode) {
@@ -253,7 +253,7 @@ export class LikeC4ModelBuilder {
253
253
  if (ast.isRelationExpression(astNode)) {
254
254
  return {
255
255
  source: this.parseElementExpression(astNode.source),
256
- target: this.parseElementExpression(astNode.target),
256
+ target: this.parseElementExpression(astNode.target)
257
257
  };
258
258
  }
259
259
  failExpectedNever(astNode);
package/dist/module.js CHANGED
@@ -1,10 +1,11 @@
1
- import { createDefaultModule, inject, createDefaultSharedModule } from 'langium';
1
+ import { createDefaultModule, createDefaultSharedModule, inject } from 'langium';
2
2
  import { LikeC4GeneratedModule, LikeC4GeneratedSharedModule } from './generated/module';
3
- import { LikeC4ScopeComputation, LikeC4ScopeProvider } from './references';
3
+ import { LikeC4DocumentSymbolProvider, LikeC4HoverProvider, LikeC4SemanticTokenProvider } from './lsp';
4
4
  import { FqnIndex, LikeC4ModelBuilder, LikeC4ModelLocator } from './model';
5
- import { registerValidationChecks } from './validation';
6
- import { LikeC4CodeLensProvider, LikeC4DocumentSymbolProvider, LikeC4HoverProvider, LikeC4SemanticTokenProvider } from './lsp';
5
+ import { LikeC4ScopeComputation, LikeC4ScopeProvider } from './references';
7
6
  import { registerProtocolHandlers } from './registerProtocolHandlers';
7
+ import { LikeC4CodeLensProvider, LikeC4WorkspaceManager } from './shared';
8
+ import { registerValidationChecks } from './validation';
8
9
  function bind(Type) {
9
10
  return (services) => new Type(services);
10
11
  }
@@ -12,7 +13,7 @@ export const LikeC4Module = {
12
13
  likec4: {
13
14
  FqnIndex: bind(FqnIndex),
14
15
  ModelBuilder: bind(LikeC4ModelBuilder),
15
- ModelLocator: bind(LikeC4ModelLocator),
16
+ ModelLocator: bind(LikeC4ModelLocator)
16
17
  // Model: bind(LikeC4Model),
17
18
  // Model: bind(LikeC4Model),
18
19
  // SpecIndex: bind(LikeC4SpecIndex),
@@ -21,7 +22,7 @@ export const LikeC4Module = {
21
22
  lsp: {
22
23
  DocumentSymbolProvider: bind(LikeC4DocumentSymbolProvider),
23
24
  SemanticTokenProvider: bind(LikeC4SemanticTokenProvider),
24
- HoverProvider: bind(LikeC4HoverProvider),
25
+ HoverProvider: bind(LikeC4HoverProvider)
25
26
  },
26
27
  //
27
28
  // // Formatter: bind(LikeC4Formatter),
@@ -34,8 +35,11 @@ export const LikeC4Module = {
34
35
  };
35
36
  const LikeC4SharedModule = {
36
37
  ...LikeC4GeneratedSharedModule,
38
+ workspace: {
39
+ WorkspaceManager: services => new LikeC4WorkspaceManager(services)
40
+ },
37
41
  lsp: {
38
- CodeLensProvider: (services) => new LikeC4CodeLensProvider(services)
42
+ CodeLensProvider: services => new LikeC4CodeLensProvider(services)
39
43
  }
40
44
  };
41
45
  export function createLanguageServices(context) {
@@ -9,7 +9,7 @@ export declare class LikeC4ScopeProvider extends DefaultScopeProvider {
9
9
  getScope(context: ReferenceInfo): Scope;
10
10
  protected computeScope(node: AstNode, referenceType: string): Scope;
11
11
  /**
12
- * Create a global scope filtered for the given reference type.
13
- */
12
+ * Create a global scope filtered for the given reference type.
13
+ */
14
14
  protected getGlobalScope(referenceType: string): Scope;
15
15
  }
@@ -19,7 +19,7 @@ export class LikeC4ScopeProvider extends DefaultScopeProvider {
19
19
  return this.fqnIndex.uniqueDescedants(fqn).iterator();
20
20
  }
21
21
  return null;
22
- }, (iterator) => {
22
+ }, iterator => {
23
23
  if (iterator) {
24
24
  return iterator.next();
25
25
  }
@@ -37,7 +37,7 @@ export class LikeC4ScopeProvider extends DefaultScopeProvider {
37
37
  return this.fqnIndex.uniqueDescedants(fqn).iterator();
38
38
  }
39
39
  return null;
40
- }, (iterator) => {
40
+ }, iterator => {
41
41
  if (iterator) {
42
42
  return iterator.next();
43
43
  }
@@ -102,8 +102,8 @@ export class LikeC4ScopeProvider extends DefaultScopeProvider {
102
102
  }, this.getGlobalScope(referenceType));
103
103
  }
104
104
  /**
105
- * Create a global scope filtered for the given reference type.
106
- */
105
+ * Create a global scope filtered for the given reference type.
106
+ */
107
107
  getGlobalScope(referenceType) {
108
108
  return new StreamScope(this.indexManager.allElements(referenceType));
109
109
  }
@@ -9,7 +9,7 @@ export class LikeC4CodeLensProvider {
9
9
  if (!isParsedLikeC4LangiumDocument(doc)) {
10
10
  return;
11
11
  }
12
- return doc.parseResult.value.views?.views.flatMap((ast) => {
12
+ return doc.parseResult.value.views?.views.flatMap(ast => {
13
13
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
14
14
  const viewId = ElementViewOps.readId(ast);
15
15
  const range = ast.$cstNode?.range;
@@ -27,7 +27,7 @@ export class LikeC4CodeLensProvider {
27
27
  command: {
28
28
  command: 'likec4.open-preview',
29
29
  arguments: [viewId],
30
- title: 'open preview',
30
+ title: 'open preview'
31
31
  }
32
32
  };
33
33
  });
@@ -0,0 +1,13 @@
1
+ import type { LangiumDocument, LangiumDocumentFactory, LangiumSharedServices } from 'langium';
2
+ import { DefaultWorkspaceManager } from 'langium';
3
+ import type { WorkspaceFolder } from 'vscode-languageserver-protocol';
4
+ export declare class LikeC4WorkspaceManager extends DefaultWorkspaceManager {
5
+ protected readonly documentFactory: LangiumDocumentFactory;
6
+ constructor(services: LangiumSharedServices);
7
+ /**
8
+ * Load all additional documents that shall be visible in the context of the given workspace
9
+ * folders and add them to the collector. This can be used to include built-in libraries of
10
+ * your language, which can be either loaded from provided files or constructed in memory.
11
+ */
12
+ protected loadAdditionalDocuments(folders: WorkspaceFolder[], collector: (document: LangiumDocument) => void): Promise<void>;
13
+ }
@@ -0,0 +1,19 @@
1
+ import { DefaultWorkspaceManager } from 'langium';
2
+ import { URI } from 'vscode-uri';
3
+ import * as builtin from '../builtin';
4
+ export class LikeC4WorkspaceManager extends DefaultWorkspaceManager {
5
+ documentFactory;
6
+ constructor(services) {
7
+ super(services);
8
+ this.documentFactory = services.workspace.LangiumDocumentFactory;
9
+ }
10
+ /**
11
+ * Load all additional documents that shall be visible in the context of the given workspace
12
+ * folders and add them to the collector. This can be used to include built-in libraries of
13
+ * your language, which can be either loaded from provided files or constructed in memory.
14
+ */
15
+ loadAdditionalDocuments(folders, collector) {
16
+ collector(this.documentFactory.fromString(builtin.specification.document, URI.parse(builtin.specification.uri)));
17
+ return Promise.resolve();
18
+ }
19
+ }
@@ -0,0 +1,2 @@
1
+ export * from './CodeLensProvider';
2
+ export * from './WorkspaceManager';
@@ -0,0 +1,2 @@
1
+ export * from './CodeLensProvider';
2
+ export * from './WorkspaceManager';
@@ -7,8 +7,10 @@ export function createTestServices() {
7
7
  const langiumDocuments = services.shared.workspace.LangiumDocuments;
8
8
  const documentBuilder = services.shared.workspace.DocumentBuilder;
9
9
  const modelBuilder = services.likec4.ModelBuilder;
10
+ const initPromise = services.shared.workspace.WorkspaceManager.initializeWorkspace([]);
10
11
  let documentIndex = 1;
11
12
  const parse = async (input, uri) => {
13
+ await initPromise;
12
14
  uri = uri ?? `${documentIndex++}${metaData.fileExtensions[0]}`;
13
15
  const document = services.shared.workspace.LangiumDocumentFactory.fromString(input, URI.file(uri));
14
16
  langiumDocuments.addDocument(document);
@@ -16,6 +18,7 @@ export function createTestServices() {
16
18
  return document;
17
19
  };
18
20
  const validate = async (input) => {
21
+ await initPromise;
19
22
  const document = typeof input === 'string' ? await parse(input) : input;
20
23
  await documentBuilder.build([document], { validationChecks: 'all' });
21
24
  const diagnostics = document.diagnostics ?? [];
@@ -27,6 +30,7 @@ export function createTestServices() {
27
30
  };
28
31
  };
29
32
  const validateAll = async () => {
33
+ await initPromise;
30
34
  const docs = langiumDocuments.all.toArray();
31
35
  await documentBuilder.build(docs, { validationChecks: 'all' });
32
36
  const diagnostics = docs.flatMap(doc => doc.diagnostics ?? []);
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import type { ValidationCheck } from 'langium';
2
3
  import type { ast } from '../ast';
3
4
  import type { LikeC4Services } from '../module';
@@ -5,7 +5,7 @@ export const elementChecks = (services) => {
5
5
  if (!fqn) {
6
6
  accept('error', 'Not indexed', {
7
7
  node: el,
8
- property: 'name',
8
+ property: 'name'
9
9
  });
10
10
  return;
11
11
  }
@@ -13,7 +13,7 @@ export const elementChecks = (services) => {
13
13
  if (withSameFqn.length > 1) {
14
14
  accept('error', `Duplicate element name ${el.name !== fqn ? el.name + ' (' + fqn + ')' : el.name}`, {
15
15
  node: el,
16
- property: 'name',
16
+ property: 'name'
17
17
  });
18
18
  }
19
19
  };
@@ -1,4 +1,5 @@
1
1
  import { elementChecks } from './element';
2
+ import { relationChecks } from './relation';
2
3
  import { elementKindChecks, tagChecks } from './specification';
3
4
  import { viewChecks } from './view';
4
5
  export function registerValidationChecks(services) {
@@ -15,6 +16,7 @@ export function registerValidationChecks(services) {
15
16
  ElementView: viewChecks(services),
16
17
  Element: elementChecks(services),
17
18
  ElementKind: elementKindChecks(services),
18
- Tag: tagChecks(services),
19
+ Relation: relationChecks(services),
20
+ Tag: tagChecks(services)
19
21
  });
20
22
  }
@@ -0,0 +1,4 @@
1
+ import type { ValidationCheck } from 'langium';
2
+ import type { ast } from '../ast';
3
+ import type { LikeC4Services } from '../module';
4
+ export declare const relationChecks: (services: LikeC4Services) => ValidationCheck<ast.Relation>;
@@ -0,0 +1,53 @@
1
+ import { resolveRelationPoints } from '../ast';
2
+ import { isSameHierarchy } from '@likec4/core/utils';
3
+ export const relationChecks = (services) => {
4
+ const fqnIndex = services.likec4.FqnIndex;
5
+ return (el, accept) => {
6
+ try {
7
+ 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
+ });
20
+ }
21
+ if (isSameHierarchy(source, target)) {
22
+ return accept('error', 'Invalid relation (same hierarchy)', {
23
+ node: el
24
+ });
25
+ }
26
+ }
27
+ catch (e) {
28
+ if (e instanceof Error) {
29
+ return accept('error', e.message, {
30
+ node: el
31
+ });
32
+ }
33
+ accept('error', 'Invalid relation', {
34
+ node: el
35
+ });
36
+ }
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
+ };
53
+ };
@@ -2,14 +2,15 @@ import { ast } from '../ast';
2
2
  export const elementKindChecks = (services) => {
3
3
  const index = services.shared.workspace.IndexManager;
4
4
  return (node, accept) => {
5
- const sameKinds = index.allElements(ast.ElementKind)
5
+ const sameKinds = index
6
+ .allElements(ast.ElementKind)
6
7
  .filter(n => n.name === node.name)
7
8
  .limit(2)
8
9
  .count();
9
10
  if (sameKinds > 1) {
10
11
  accept('error', `Duplicate element kind '${node.name}'`, {
11
12
  node: node,
12
- property: 'name',
13
+ property: 'name'
13
14
  });
14
15
  }
15
16
  };
@@ -17,14 +18,15 @@ export const elementKindChecks = (services) => {
17
18
  export const tagChecks = (services) => {
18
19
  const index = services.shared.workspace.IndexManager;
19
20
  return (node, accept) => {
20
- const sameKinds = index.allElements(ast.Tag)
21
+ const sameKinds = index
22
+ .allElements(ast.Tag)
21
23
  .filter(n => n.name === node.name)
22
24
  .limit(2)
23
25
  .count();
24
26
  if (sameKinds > 1) {
25
27
  accept('error', `Duplicate tag '${node.name}'`, {
26
28
  node: node,
27
- property: 'name',
29
+ property: 'name'
28
30
  });
29
31
  }
30
32
  };
@@ -5,7 +5,8 @@ export const viewChecks = (services) => {
5
5
  if (!el.name) {
6
6
  return;
7
7
  }
8
- const anotherViews = index.allElements(ast.View)
8
+ const anotherViews = index
9
+ .allElements(ast.View)
9
10
  .filter(n => n.name === el.name)
10
11
  .limit(2)
11
12
  .count();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@likec4/language-server",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "bugs": "https://github.com/likec4/likec4/issues",
6
6
  "homepage": "https://like-c4.dev",
@@ -24,6 +24,11 @@
24
24
  "types": "./dist/protocol.d.ts",
25
25
  "import": "./dist/protocol.js",
26
26
  "require": "./dist/protocol.cjs"
27
+ },
28
+ "./builtin": {
29
+ "types": "./dist/builtin.d.ts",
30
+ "import": "./dist/builtin.js",
31
+ "require": "./dist/builtin.cjs"
27
32
  }
28
33
  },
29
34
  "publishConfig": {
@@ -41,6 +46,11 @@
41
46
  "types": "./dist/protocol.d.ts",
42
47
  "import": "./dist/protocol.js",
43
48
  "require": "./dist/protocol.cjs"
49
+ },
50
+ "./builtin": {
51
+ "types": "./dist/builtin.d.ts",
52
+ "import": "./dist/builtin.js",
53
+ "require": "./dist/builtin.cjs"
44
54
  }
45
55
  }
46
56
  },
@@ -53,14 +63,14 @@
53
63
  "watch:ts": "tsc --watch",
54
64
  "generate": "langium generate",
55
65
  "build": "run-s 'build:langium' 'build:ts'",
56
- "dev": "run-p 'watch:*'",
66
+ "dev": "run generate && run-p 'watch:*'",
57
67
  "lint": "run -T eslint src/ --fix",
58
- "clean": "rimraf dist",
68
+ "clean": "run -T rimraf dist contrib",
59
69
  "test": "vitest run",
60
70
  "test:watch": "vitest"
61
71
  },
62
72
  "dependencies": {
63
- "@likec4/core": "0.4.0",
73
+ "@likec4/core": "0.5.0",
64
74
  "@mobily/ts-belt": "^3.13.1",
65
75
  "langium": "^1.1.0",
66
76
  "nanoid": "^4.0.2",
@@ -68,7 +78,7 @@
68
78
  "rambdax": "^9.1.0",
69
79
  "strip-indent": "^4.0.0",
70
80
  "tiny-invariant": "^1.3.1",
71
- "type-fest": "^3.7.2",
81
+ "type-fest": "^3.8.0",
72
82
  "vscode-languageserver-protocol": "3.17.2",
73
83
  "vscode-uri": "3.0.7"
74
84
  },
@@ -77,7 +87,7 @@
77
87
  "@types/object-hash": "^3.0.2",
78
88
  "langium-cli": "^1.1.0",
79
89
  "npm-run-all": "^4.1.5",
80
- "typescript": "^5.0.3",
90
+ "typescript": "^5.0.4",
81
91
  "vite": "^4.2.1",
82
92
  "vitest": "^0.30.1"
83
93
  }
File without changes