@likec4/language-server 0.57.1 → 0.60.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 (41) hide show
  1. package/dist/Rpc.js +9 -22
  2. package/dist/ast.d.ts +1 -0
  3. package/dist/ast.js +2 -2
  4. package/dist/browser/index.js +2 -2
  5. package/dist/generated/ast.d.ts +3 -3
  6. package/dist/generated/ast.js +322 -49
  7. package/dist/generated/grammar.d.ts +1 -1
  8. package/dist/generated/grammar.js +1 -1
  9. package/dist/generated/module.d.ts +4 -4
  10. package/dist/index.d.ts +2 -1
  11. package/dist/logger.js +4 -0
  12. package/dist/lsp/CodeLensProvider.d.ts +3 -2
  13. package/dist/lsp/CodeLensProvider.js +5 -1
  14. package/dist/lsp/DocumentHighlightProvider.d.ts +2 -1
  15. package/dist/lsp/DocumentHighlightProvider.js +1 -1
  16. package/dist/lsp/DocumentLinkProvider.d.ts +2 -1
  17. package/dist/lsp/DocumentLinkProvider.js +3 -3
  18. package/dist/lsp/DocumentSymbolProvider.d.ts +3 -2
  19. package/dist/lsp/DocumentSymbolProvider.js +6 -8
  20. package/dist/lsp/HoverProvider.d.ts +2 -1
  21. package/dist/lsp/HoverProvider.js +1 -1
  22. package/dist/lsp/SemanticTokenProvider.d.ts +2 -1
  23. package/dist/lsp/SemanticTokenProvider.js +1 -1
  24. package/dist/model/model-builder.d.ts +4 -4
  25. package/dist/model/model-builder.js +53 -43
  26. package/dist/model/model-locator.js +3 -1
  27. package/dist/model/model-parser.js +6 -3
  28. package/dist/module.d.ts +2 -1
  29. package/dist/module.js +3 -5
  30. package/dist/node/index.js +1 -1
  31. package/dist/references/scope-provider.js +9 -6
  32. package/dist/shared/NodeKindProvider.d.ts +3 -2
  33. package/dist/shared/WorkspaceSymbolProvider.d.ts +1 -1
  34. package/dist/shared/WorkspaceSymbolProvider.js +1 -1
  35. package/dist/test/testServices.d.ts +1 -0
  36. package/dist/test/testServices.js +39 -30
  37. package/dist/validation/element.js +14 -10
  38. package/dist/validation/index.js +16 -22
  39. package/dist/validation/specification.d.ts +1 -1
  40. package/dist/validation/specification.js +38 -9
  41. package/package.json +9 -9
@@ -4,7 +4,7 @@ import {
4
4
  parentFqn
5
5
  } from "@likec4/core";
6
6
  import { computeView, LikeC4ModelGraph } from "@likec4/graph";
7
- import { DocumentState } from "langium";
7
+ import { DocumentState, interruptAndCheck } from "langium";
8
8
  import * as R from "remeda";
9
9
  import { Disposable } from "vscode-languageserver";
10
10
  import { isParsedLikeC4LangiumDocument } from "../ast.js";
@@ -36,6 +36,7 @@ function buildModel(services, docs) {
36
36
  ...parsed
37
37
  };
38
38
  }
39
+ logger.warn(`No kind '${parsed.kind}' found for ${parsed.id}`);
39
40
  } catch (e) {
40
41
  logWarnError(e);
41
42
  }
@@ -63,34 +64,38 @@ function buildModel(services, docs) {
63
64
  {}
64
65
  )
65
66
  );
66
- const toModelRelation = ({
67
- astPath,
68
- source,
69
- target,
70
- kind,
71
- ...model
72
- }) => {
73
- if (source in elements && target in elements) {
74
- if (!!kind && kind in c4Specification.relationships) {
67
+ const toModelRelation = (doc) => {
68
+ return ({
69
+ astPath,
70
+ source,
71
+ target,
72
+ kind,
73
+ links,
74
+ ...model
75
+ }) => {
76
+ if (source in elements && target in elements) {
77
+ if (!!kind && kind in c4Specification.relationships) {
78
+ return {
79
+ source,
80
+ target,
81
+ kind,
82
+ ...links && { links: resolveLinks(doc, links) },
83
+ ...c4Specification.relationships[kind],
84
+ ...model
85
+ };
86
+ }
75
87
  return {
76
88
  source,
77
89
  target,
78
- kind,
79
- ...c4Specification.relationships[kind],
90
+ ...links && { links: resolveLinks(doc, links) },
80
91
  ...model
81
92
  };
82
93
  }
83
- return {
84
- source,
85
- target,
86
- ...model
87
- };
88
- }
89
- return null;
94
+ return null;
95
+ };
90
96
  };
91
97
  const relations = R.pipe(
92
- R.flatMap(docs, (d) => d.c4Relations),
93
- R.map(toModelRelation),
98
+ R.flatMap(docs, (d) => R.map(d.c4Relations, toModelRelation(d))),
94
99
  R.compact,
95
100
  R.mapToObj((r) => [r.id, r])
96
101
  );
@@ -160,9 +165,14 @@ export class LikeC4ModelBuilder {
160
165
  services.shared.workspace.DocumentBuilder.onBuildPhase(
161
166
  DocumentState.Validated,
162
167
  async (docs, _cancelToken) => {
163
- logger.debug(`[ModelBuilder] onValidated (${docs.length} docs)
168
+ let parsed = [];
169
+ try {
170
+ logger.debug(`[ModelBuilder] onValidated (${docs.length} docs)
164
171
  ${printDocs(docs)}`);
165
- const parsed = parser.parse(docs).map((d) => d.uri);
172
+ parsed.push(...parser.parse(docs).map((d) => d.uri));
173
+ } catch (e) {
174
+ logger.error(e);
175
+ }
166
176
  if (parsed.length > 0) {
167
177
  this.notifyListeners(parsed);
168
178
  }
@@ -173,10 +183,13 @@ ${printDocs(docs)}`);
173
183
  }
174
184
  langiumDocuments;
175
185
  listeners = [];
176
- buildRawModel() {
177
- const cache = this.services.WorkspaceCache;
178
- return cache.get(RAW_MODEL_CACHE, () => {
179
- try {
186
+ async buildRawModel(cancelToken) {
187
+ return await this.services.shared.workspace.WorkspaceLock.read(async () => {
188
+ if (cancelToken) {
189
+ await interruptAndCheck(cancelToken);
190
+ }
191
+ const cache = this.services.WorkspaceCache;
192
+ return cache.get(RAW_MODEL_CACHE, () => {
180
193
  const docs = this.documents();
181
194
  if (docs.length === 0) {
182
195
  logger.debug("[ModelBuilder] No documents to build model from");
@@ -185,21 +198,18 @@ ${printDocs(docs)}`);
185
198
  logger.debug(`[ModelBuilder] buildModel from ${docs.length} docs:
186
199
  ${printDocs(docs)}`);
187
200
  return buildModel(this.services, docs);
188
- } catch (e) {
189
- logError(e);
190
- return null;
191
- }
201
+ });
192
202
  });
193
203
  }
194
204
  previousViews = {};
195
- buildModel() {
205
+ async buildModel(cancelToken) {
206
+ const model = await this.buildRawModel(cancelToken);
207
+ if (!model) {
208
+ return null;
209
+ }
196
210
  const cache = this.services.WorkspaceCache;
197
211
  const viewsCache = this.services.WorkspaceCache;
198
212
  return cache.get(MODEL_CACHE, () => {
199
- const model = this.buildRawModel();
200
- if (!model) {
201
- return null;
202
- }
203
213
  const index = new LikeC4ModelGraph(model);
204
214
  const allViews = [];
205
215
  for (const view of R.values(model.views)) {
@@ -225,15 +235,15 @@ ${printDocs(docs)}`);
225
235
  };
226
236
  });
227
237
  }
228
- computeView(viewId) {
238
+ async computeView(viewId, cancelToken) {
239
+ const model = await this.buildRawModel(cancelToken);
240
+ const view = model?.views[viewId];
241
+ if (!view) {
242
+ logger.warn(`[ModelBuilder] Cannot find view ${viewId}`);
243
+ return null;
244
+ }
229
245
  const cache = this.services.WorkspaceCache;
230
246
  return cache.get(computedViewKey(viewId), () => {
231
- const model = this.buildRawModel();
232
- const view = model?.views[viewId];
233
- if (!view) {
234
- logger.warn(`[ModelBuilder] Cannot find view ${viewId}`);
235
- return null;
236
- }
237
247
  const index = new LikeC4ModelGraph(model);
238
248
  const result = computeView(view, index);
239
249
  if (!result.isSuccess) {
@@ -1,5 +1,7 @@
1
- import { findNodeForKeyword, findNodeForProperty, getDocument } from "langium";
1
+ import { AstUtils, GrammarUtils } from "langium";
2
2
  import { ast, isParsedLikeC4LangiumDocument } from "../ast.js";
3
+ const { findNodeForKeyword, findNodeForProperty } = GrammarUtils;
4
+ const { getDocument } = AstUtils;
3
5
  export class LikeC4ModelLocator {
4
6
  constructor(services) {
5
7
  this.services = services;
@@ -1,5 +1,5 @@
1
1
  import { InvalidModelError, invariant, isNonEmptyArray, nonexhaustive } from "@likec4/core";
2
- import { getDocument } from "langium";
2
+ import { AstUtils } from "langium";
3
3
  import objectHash from "object-hash";
4
4
  import { isTruthy } from "remeda";
5
5
  import stripIndent from "strip-indent";
@@ -18,6 +18,7 @@ import {
18
18
  } from "../ast.js";
19
19
  import { elementRef, getFqnElementRef } from "../elementRef.js";
20
20
  import { logError, logger, logWarnError } from "../logger.js";
21
+ const { getDocument } = AstUtils;
21
22
  function toSingleLine(str) {
22
23
  return str ? removeIndent(str).split("\n").join(" ") : void 0;
23
24
  }
@@ -111,7 +112,7 @@ export class LikeC4ModelParser {
111
112
  const stylePropsAst = astNode.body?.props.find(ast.isStyleProperties)?.props;
112
113
  const styleProps = toElementStyleExcludeDefaults(stylePropsAst);
113
114
  const astPath = this.getAstNodePath(astNode);
114
- let [title, description, technology] = astNode.props;
115
+ let [title, description, technology] = astNode.props ?? [];
115
116
  const bodyProps = astNode.body?.props.filter(ast.isElementStringProperty) ?? [];
116
117
  title = toSingleLine(title ?? bodyProps.find((p) => p.key === "title")?.value);
117
118
  description = removeIndent(description ?? bodyProps.find((p) => p.key === "description")?.value);
@@ -134,6 +135,7 @@ export class LikeC4ModelParser {
134
135
  const target = this.resolveFqn(coupling.target);
135
136
  const source = this.resolveFqn(coupling.source);
136
137
  const tags = this.convertTags(astNode) ?? this.convertTags(astNode.body);
138
+ const links = astNode.body?.props.filter(ast.isLinkProperty).map((p) => p.value);
137
139
  const kind = astNode.kind?.ref?.name;
138
140
  const astPath = this.getAstNodePath(astNode);
139
141
  const title = toSingleLine(
@@ -153,6 +155,7 @@ export class LikeC4ModelParser {
153
155
  title,
154
156
  ...kind && { kind },
155
157
  ...tags && { tags },
158
+ ...isNonEmptyArray(links) && { links },
156
159
  ...toRelationshipStyleExcludeDefaults(styleProp?.props)
157
160
  };
158
161
  }
@@ -372,6 +375,6 @@ export class LikeC4ModelParser {
372
375
  return null;
373
376
  }
374
377
  const tags = withTags.tags?.value.flatMap(({ ref }) => ref ? ref.name : []);
375
- return tags && isNonEmptyArray(tags) ? tags : null;
378
+ return isNonEmptyArray(tags) ? tags : null;
376
379
  }
377
380
  }
package/dist/module.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { type DefaultSharedModuleContext, type LangiumServices, type LangiumSharedServices, type Module, type PartialLangiumServices, WorkspaceCache } from 'langium';
1
+ import { type Module, WorkspaceCache } from 'langium';
2
+ import { type DefaultSharedModuleContext, type LangiumServices, type LangiumSharedServices, type PartialLangiumServices } from 'langium/lsp';
2
3
  import { LikeC4CodeLensProvider, LikeC4DocumentHighlightProvider, LikeC4DocumentLinkProvider, LikeC4DocumentSymbolProvider, LikeC4HoverProvider, LikeC4SemanticTokenProvider } from './lsp';
3
4
  import { FqnIndex, LikeC4ModelBuilder, LikeC4ModelLocator, LikeC4ModelParser } from './model';
4
5
  import { LikeC4ScopeComputation, LikeC4ScopeProvider } from './references';
package/dist/module.js CHANGED
@@ -1,11 +1,9 @@
1
1
  import { normalizeError } from "@likec4/core";
2
+ import { EmptyFileSystem, inject, WorkspaceCache } from "langium";
2
3
  import {
3
4
  createDefaultModule,
4
- createDefaultSharedModule,
5
- EmptyFileSystem,
6
- inject,
7
- WorkspaceCache
8
- } from "langium";
5
+ createDefaultSharedModule
6
+ } from "langium/lsp";
9
7
  import { LikeC4GeneratedModule, LikeC4GeneratedSharedModule } from "./generated/module.js";
10
8
  import { logger } from "./logger.js";
11
9
  import {
@@ -1,4 +1,4 @@
1
- import { startLanguageServer as startLanguim } from "langium";
1
+ import { startLanguageServer as startLanguim } from "langium/lsp";
2
2
  import { NodeFileSystem } from "langium/node";
3
3
  import { createConnection, ProposedFeatures } from "vscode-languageserver/node";
4
4
  import { createLanguageServices } from "../module.js";
@@ -1,17 +1,20 @@
1
1
  import {
2
- DONE_RESULT,
2
+ AstUtils,
3
+ CstUtils,
3
4
  DefaultScopeProvider,
5
+ DONE_RESULT,
4
6
  EMPTY_STREAM,
5
- StreamImpl,
6
- StreamScope,
7
- findNodeForProperty,
8
- getDocument,
7
+ GrammarUtils,
9
8
  stream,
10
- toDocumentSegment
9
+ StreamImpl,
10
+ StreamScope
11
11
  } from "langium";
12
12
  import { ast } from "../ast.js";
13
13
  import { elementRef, getFqnElementRef } from "../elementRef.js";
14
14
  import { logError } from "../logger.js";
15
+ const { findNodeForProperty } = GrammarUtils;
16
+ const { toDocumentSegment } = CstUtils;
17
+ const { getDocument } = AstUtils;
15
18
  function toAstNodeDescription(entry) {
16
19
  const $cstNode = findNodeForProperty(entry.el.$cstNode, "name");
17
20
  return {
@@ -1,6 +1,7 @@
1
- import { type AstNode, type AstNodeDescription, type LangiumSharedServices } from 'langium';
1
+ import { type AstNode, type AstNodeDescription } from 'langium';
2
+ import type { LangiumSharedServices, NodeKindProvider as LspNodeKindProvider } from 'langium/lsp';
2
3
  import { CompletionItemKind, SymbolKind } from 'vscode-languageserver-protocol';
3
- export declare class NodeKindProvider implements NodeKindProvider {
4
+ export declare class NodeKindProvider implements LspNodeKindProvider {
4
5
  private services;
5
6
  constructor(services: LangiumSharedServices);
6
7
  /**
@@ -1,4 +1,4 @@
1
- import { DefaultWorkspaceSymbolProvider } from 'langium';
1
+ import { DefaultWorkspaceSymbolProvider } from 'langium/lsp';
2
2
  export declare class WorkspaceSymbolProvider extends DefaultWorkspaceSymbolProvider {
3
3
  }
4
4
  //# sourceMappingURL=WorkspaceSymbolProvider.d.ts.map
@@ -1,3 +1,3 @@
1
- import { DefaultWorkspaceSymbolProvider } from "langium";
1
+ import { DefaultWorkspaceSymbolProvider } from "langium/lsp";
2
2
  export class WorkspaceSymbolProvider extends DefaultWorkspaceSymbolProvider {
3
3
  }
@@ -15,6 +15,7 @@ export declare function createTestServices(workspace?: string): {
15
15
  warnings: string[];
16
16
  }>;
17
17
  buildModel: () => Promise<import("@likec4/core").LikeC4Model>;
18
+ resetState: () => Promise<void>;
18
19
  };
19
20
  export type TestServices = ReturnType<typeof createTestServices>;
20
21
  export type TestParseFn = TestServices['validate'];
@@ -1,4 +1,4 @@
1
- import { EmptyFileSystem } from "langium";
1
+ import { DocumentState, EmptyFileSystem } from "langium";
2
2
  import * as assert from "node:assert";
3
3
  import stripIndent from "strip-indent";
4
4
  import { DiagnosticSeverity } from "vscode-languageserver-protocol";
@@ -19,10 +19,15 @@ export function createTestServices(workspace = "file:///test/workspace") {
19
19
  let documentIndex = 1;
20
20
  const parse = async (input, uri) => {
21
21
  if (!isInitialized) {
22
- isInitialized = true;
23
- await services.shared.workspace.WorkspaceManager.initializeWorkspace([workspaceFolder]);
24
- Object.assign(services.shared.workspace.WorkspaceManager, {
25
- folders: [workspaceFolder]
22
+ await services.shared.workspace.WorkspaceLock.write(async (_cancelToken) => {
23
+ if (isInitialized) {
24
+ return;
25
+ }
26
+ isInitialized = true;
27
+ await services.shared.workspace.WorkspaceManager.initializeWorkspace([workspaceFolder]);
28
+ Object.assign(services.shared.workspace.WorkspaceManager, {
29
+ folders: [workspaceFolder]
30
+ });
26
31
  });
27
32
  }
28
33
  const docUri = Utils.resolvePath(
@@ -35,12 +40,16 @@ export function createTestServices(workspace = "file:///test/workspace") {
35
40
  docUri
36
41
  );
37
42
  langiumDocuments.addDocument(document);
38
- await documentBuilder.build([document], { validation: false });
43
+ await services.shared.workspace.WorkspaceLock.write(async (_cancelToken) => {
44
+ await documentBuilder.build([document], { validation: false });
45
+ });
39
46
  return document;
40
47
  };
41
48
  const validate = async (input, uri) => {
42
49
  const document = typeof input === "string" ? await parse(input, uri) : input;
43
- await documentBuilder.build([document], { validation: true });
50
+ await services.shared.workspace.WorkspaceLock.write(async (_cancelToken) => {
51
+ await documentBuilder.build([document], { validation: true });
52
+ });
44
53
  const diagnostics = document.diagnostics ?? [];
45
54
  const warnings = diagnostics.flatMap((d) => d.severity === DiagnosticSeverity.Warning ? d.message : []);
46
55
  const errors = diagnostics.flatMap((d) => d.severity === DiagnosticSeverity.Error ? d.message : []);
@@ -53,41 +62,41 @@ export function createTestServices(workspace = "file:///test/workspace") {
53
62
  };
54
63
  let previousPromise = Promise.resolve();
55
64
  const validateAll = async () => {
56
- const currentPromise = previousPromise.then(async () => {
57
- const docs = langiumDocuments.all.toArray();
58
- assert.ok(docs.length > 0, "no documents to validate");
59
- await documentBuilder.build(docs, { validation: true });
60
- const diagnostics = docs.flatMap((doc) => doc.diagnostics ?? []);
61
- const warnings = diagnostics.flatMap((d) => d.severity === DiagnosticSeverity.Warning ? d.message : []);
62
- const errors = diagnostics.flatMap((d) => d.severity === DiagnosticSeverity.Error ? d.message : []);
63
- return {
64
- diagnostics,
65
- errors,
66
- warnings
67
- };
68
- }).catch((e) => {
69
- console.error(e);
70
- return Promise.resolve({
71
- diagnostics: [],
72
- errors: [],
73
- warnings: []
74
- });
65
+ await services.shared.workspace.WorkspaceLock.write(async (_cancelToken) => {
66
+ const docs2 = langiumDocuments.all.toArray();
67
+ await documentBuilder.build(docs2, { validation: true });
75
68
  });
76
- previousPromise = currentPromise;
77
- return await currentPromise;
69
+ await documentBuilder.waitUntil(DocumentState.Validated);
70
+ const docs = langiumDocuments.all.toArray();
71
+ assert.ok(docs.length > 0, "no documents to validate");
72
+ const diagnostics = docs.flatMap((doc) => doc.diagnostics ?? []);
73
+ const warnings = diagnostics.flatMap((d) => d.severity === DiagnosticSeverity.Warning ? d.message : []);
74
+ const errors = diagnostics.flatMap((d) => d.severity === DiagnosticSeverity.Error ? d.message : []);
75
+ return {
76
+ diagnostics,
77
+ errors,
78
+ warnings
79
+ };
78
80
  };
79
81
  const buildModel = async () => {
80
82
  await validateAll();
81
- const model = modelBuilder.buildModel();
83
+ const model = await modelBuilder.buildModel();
82
84
  if (!model)
83
85
  throw new Error("No model found");
84
86
  return model;
85
87
  };
88
+ const resetState = async () => {
89
+ await services.shared.workspace.WorkspaceLock.write(async (cancelToken) => {
90
+ const docs = langiumDocuments.all.toArray().map((doc) => doc.uri);
91
+ await documentBuilder.update([], docs, cancelToken);
92
+ });
93
+ };
86
94
  return {
87
95
  services,
88
96
  parse,
89
97
  validate,
90
98
  validateAll,
91
- buildModel
99
+ buildModel,
100
+ resetState
92
101
  };
93
102
  }
@@ -1,4 +1,5 @@
1
- import { getDocument } from "langium";
1
+ import { AstUtils } from "langium";
2
+ const { getDocument } = AstUtils;
2
3
  export const elementChecks = (services) => {
3
4
  const fqnIndex = services.likec4.FqnIndex;
4
5
  return (el, accept) => {
@@ -12,21 +13,24 @@ export const elementChecks = (services) => {
12
13
  }
13
14
  const withSameFqn = fqnIndex.byFqn(fqn).filter((v) => v.el !== el).head();
14
15
  if (withSameFqn) {
16
+ const isAnotherDoc = withSameFqn.doc.uri !== getDocument(el).uri;
15
17
  accept(
16
18
  "error",
17
19
  `Duplicate element name ${el.name !== fqn ? el.name + " (" + fqn + ")" : el.name}`,
18
20
  {
19
21
  node: el,
20
22
  property: "name",
21
- relatedInformation: [
22
- {
23
- location: {
24
- range: withSameFqn.el.$cstNode.range,
25
- uri: getDocument(withSameFqn.el).uri.toString()
26
- },
27
- message: `Already defined here`
28
- }
29
- ]
23
+ ...isAnotherDoc && {
24
+ relatedInformation: [
25
+ {
26
+ location: {
27
+ range: withSameFqn.el.$cstNode.range,
28
+ uri: withSameFqn.doc.uri.toString()
29
+ },
30
+ message: `conflicting element`
31
+ }
32
+ ]
33
+ }
30
34
  }
31
35
  );
32
36
  }
@@ -10,35 +10,29 @@ import {
10
10
  tagChecks
11
11
  } from "./specification.js";
12
12
  import { viewChecks } from "./view.js";
13
- import {
14
- customElementExprChecks,
15
- incomingExpressionChecks,
16
- outgoingExpressionChecks
17
- } from "./view-predicates/index.js";
13
+ import { customElementExprChecks, incomingExpressionChecks, outgoingExpressionChecks } from "./view-predicates/index.js";
18
14
  export function registerValidationChecks(services) {
19
15
  logger.info("registerValidationChecks");
20
16
  const registry = services.validation.ValidationRegistry;
21
- registry.register(
22
- {
23
- SpecificationRule: specificationRuleChecks(services),
24
- Model: modelRuleChecks(services),
25
- ModelViews: modelViewsChecks(services),
26
- ElementView: viewChecks(services),
27
- Element: elementChecks(services),
28
- ElementKind: elementKindChecks(services),
29
- Relation: relationChecks(services),
30
- Tag: tagChecks(services),
31
- CustomElementExpr: customElementExprChecks(services),
32
- RelationshipKind: relationshipChecks(services),
33
- IncomingExpr: incomingExpressionChecks(services),
34
- OutgoingExpr: outgoingExpressionChecks(services)
35
- },
36
- "slow"
37
- );
17
+ registry.register({
18
+ SpecificationRule: specificationRuleChecks(services),
19
+ Model: modelRuleChecks(services),
20
+ ModelViews: modelViewsChecks(services),
21
+ ElementView: viewChecks(services),
22
+ Element: elementChecks(services),
23
+ ElementKind: elementKindChecks(services),
24
+ Relation: relationChecks(services),
25
+ Tag: tagChecks(services),
26
+ CustomElementExpr: customElementExprChecks(services),
27
+ RelationshipKind: relationshipChecks(services),
28
+ IncomingExpr: incomingExpressionChecks(services),
29
+ OutgoingExpr: outgoingExpressionChecks(services)
30
+ });
38
31
  const connection = services.shared.lsp.Connection;
39
32
  if (connection) {
40
33
  services.shared.workspace.DocumentBuilder.onUpdate((_, deleted) => {
41
34
  for (const uri of deleted) {
35
+ logger.debug(`clear diagnostics for deleted ${uri.path}`);
42
36
  void connection.sendDiagnostics({
43
37
  uri: uri.toString(),
44
38
  diagnostics: []
@@ -1,4 +1,4 @@
1
- import type { ValidationCheck } from 'langium';
1
+ import { type ValidationCheck } from 'langium';
2
2
  import { ast } from '../ast';
3
3
  import type { LikeC4Services } from '../module';
4
4
  export declare const specificationRuleChecks: (_: LikeC4Services) => ValidationCheck<ast.SpecificationRule>;
@@ -1,3 +1,4 @@
1
+ import { AstUtils } from "langium";
1
2
  import { ast } from "../ast.js";
2
3
  export const specificationRuleChecks = (_) => {
3
4
  return (node, accept) => {
@@ -32,11 +33,23 @@ export const modelViewsChecks = (_) => {
32
33
  export const elementKindChecks = (services) => {
33
34
  const index = services.shared.workspace.IndexManager;
34
35
  return (node, accept) => {
35
- const sameKinds = index.allElements(ast.ElementKind).filter((n) => n.name === node.name).limit(2).count();
36
- if (sameKinds > 1) {
36
+ const sameKind = index.allElements(ast.ElementKind).filter((n) => n.name === node.name && n.node !== node).head();
37
+ if (sameKind) {
38
+ const isAnotherDoc = sameKind.documentUri !== AstUtils.getDocument(node).uri;
37
39
  accept("error", `Duplicate element kind '${node.name}'`, {
38
40
  node,
39
- property: "name"
41
+ property: "name",
42
+ ...isAnotherDoc && {
43
+ relatedInformation: [
44
+ {
45
+ location: {
46
+ range: sameKind.nameSegment.range,
47
+ uri: sameKind.documentUri.toString()
48
+ },
49
+ message: `conflicting definition`
50
+ }
51
+ ]
52
+ }
40
53
  });
41
54
  }
42
55
  };
@@ -45,12 +58,28 @@ export const tagChecks = (services) => {
45
58
  const index = services.shared.workspace.IndexManager;
46
59
  return (node, accept) => {
47
60
  const tagname = "#" + node.name;
48
- const sameKinds = index.allElements(ast.Tag).filter((n) => n.name === tagname).limit(2).count();
49
- if (sameKinds > 1) {
50
- accept("error", `Duplicate tag '${node.name}'`, {
51
- node,
52
- property: "name"
53
- });
61
+ const sameTag = index.allElements(ast.Tag).filter((n) => n.name === tagname && n.node !== node).head();
62
+ if (sameTag) {
63
+ const isAnotherDoc = sameTag.documentUri !== AstUtils.getDocument(node).uri;
64
+ accept(
65
+ "error",
66
+ `Duplicate tag '${node.name}'`,
67
+ {
68
+ node,
69
+ property: "name",
70
+ ...isAnotherDoc && {
71
+ relatedInformation: [
72
+ {
73
+ location: {
74
+ range: sameTag.nameSegment.range,
75
+ uri: sameTag.documentUri.toString()
76
+ },
77
+ message: `conflicting definition`
78
+ }
79
+ ]
80
+ }
81
+ }
82
+ );
54
83
  }
55
84
  };
56
85
  };
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.57.1",
4
+ "version": "0.60.0",
5
5
  "license": "MIT",
6
6
  "bugs": "https://github.com/likec4/likec4/issues",
7
7
  "homepage": "https://likec4.dev",
@@ -66,32 +66,32 @@
66
66
  "test": "vitest run"
67
67
  },
68
68
  "dependencies": {
69
- "@likec4/core": "0.57.1",
70
- "@likec4/graph": "0.57.1",
69
+ "@likec4/core": "0.60.0",
70
+ "@likec4/graph": "0.60.0",
71
71
  "debounce-fn": "^6.0.0",
72
- "langium": "^2.1.3",
72
+ "langium": "^3.0.0",
73
73
  "object-hash": "^3.0.0",
74
74
  "p-debounce": "^4.0.0",
75
75
  "rambdax": "^9.1.1",
76
76
  "remeda": "^1.40.1",
77
77
  "strip-indent": "^4.0.0",
78
- "type-fest": "^4.10.2",
78
+ "type-fest": "^4.10.3",
79
79
  "ufo": "^1.3.2",
80
80
  "vscode-languageserver": "9.0.1",
81
81
  "vscode-languageserver-protocol": "3.17.5",
82
82
  "vscode-uri": "3.0.8"
83
83
  },
84
84
  "devDependencies": {
85
- "@types/node": "^20.11.17",
85
+ "@types/node": "^20.11.19",
86
86
  "@types/object-hash": "^3.0.6",
87
87
  "execa": "^8.0.1",
88
- "langium-cli": "^2.1.0",
88
+ "langium-cli": "^3.0.1",
89
89
  "npm-run-all2": "^5.0.2",
90
90
  "typescript": "^5.3.3",
91
91
  "unbuild": "^2.0.0",
92
- "vitest": "^1.2.2"
92
+ "vitest": "^1.3.1"
93
93
  },
94
- "packageManager": "yarn@4.1.0",
94
+ "packageManager": "yarn@4.1.1",
95
95
  "volta": {
96
96
  "extends": "../../package.json"
97
97
  }