@likec4/language-server 1.20.1 → 1.20.3

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 (56) hide show
  1. package/README.md +19 -0
  2. package/bin/likec4-language-server.mjs +5 -0
  3. package/dist/LikeC4FileSystem.js +9 -9
  4. package/dist/Rpc.d.ts +2 -4
  5. package/dist/Rpc.js +27 -36
  6. package/dist/ast.d.ts +1 -0
  7. package/dist/ast.js +5 -1
  8. package/dist/bundled.mjs +6041 -0
  9. package/dist/formatting/LikeC4Formatter.d.ts +9 -0
  10. package/dist/formatting/LikeC4Formatter.js +131 -14
  11. package/dist/generated/ast.d.ts +13 -2
  12. package/dist/generated/ast.js +18 -1
  13. package/dist/generated/grammar.js +1 -1
  14. package/dist/logger.d.ts +3 -0
  15. package/dist/logger.js +0 -4
  16. package/dist/lsp/CompletionProvider.js +11 -3
  17. package/dist/model/deployments-index.d.ts +2 -1
  18. package/dist/model/deployments-index.js +3 -10
  19. package/dist/model/fqn-index.d.ts +2 -1
  20. package/dist/model/fqn-index.js +24 -17
  21. package/dist/model/model-builder.d.ts +2 -1
  22. package/dist/model/model-builder.js +32 -30
  23. package/dist/model/model-parser.d.ts +1 -1
  24. package/dist/model/model-parser.js +9 -6
  25. package/dist/model/parser/PredicatesParser.js +7 -1
  26. package/dist/utils/disposable.d.ts +8 -0
  27. package/dist/utils/disposable.js +25 -0
  28. package/dist/utils/index.d.ts +1 -0
  29. package/dist/utils/index.js +1 -0
  30. package/dist/validation/_shared.js +4 -1
  31. package/dist/validation/index.d.ts +2 -2
  32. package/dist/validation/index.js +4 -1
  33. package/dist/validation/specification.d.ts +1 -0
  34. package/dist/validation/specification.js +30 -0
  35. package/package.json +33 -27
  36. package/src/LikeC4FileSystem.ts +14 -13
  37. package/src/Rpc.ts +28 -38
  38. package/src/ast.ts +6 -1
  39. package/src/formatting/LikeC4Formatter.ts +198 -17
  40. package/src/generated/ast.ts +35 -2
  41. package/src/generated/grammar.ts +1 -1
  42. package/src/like-c4.langium +14 -3
  43. package/src/logger.ts +3 -4
  44. package/src/lsp/CompletionProvider.ts +27 -18
  45. package/src/model/deployments-index.ts +4 -17
  46. package/src/model/fqn-index.ts +26 -19
  47. package/src/model/model-builder.ts +32 -31
  48. package/src/model/model-parser.ts +14 -11
  49. package/src/model/parser/PredicatesParser.ts +30 -24
  50. package/src/utils/disposable.ts +30 -0
  51. package/src/utils/index.ts +1 -0
  52. package/src/validation/_shared.ts +5 -2
  53. package/src/validation/index.ts +6 -2
  54. package/src/validation/specification.ts +34 -0
  55. package/contrib/likec4.tmLanguage.json +0 -73
  56. package/dist/like-c4.langium +0 -852
package/dist/logger.d.ts CHANGED
@@ -2,6 +2,9 @@ import { LogLevels } from '@likec4/log';
2
2
  import type { Connection } from 'vscode-languageserver';
3
3
  export declare const logger: any;
4
4
  export declare function logError(err: unknown): void;
5
+ /**
6
+ * Logs an error as warning (not critical)
7
+ */
5
8
  export declare function logWarnError(err: unknown): void;
6
9
  export declare function setLogLevel(level: keyof typeof LogLevels): void;
7
10
  export declare function logToLspConnection(connection: Connection): void;
package/dist/logger.js CHANGED
@@ -6,10 +6,6 @@ export function logError(err) {
6
6
  logger.error(err);
7
7
  }
8
8
  export function logWarnError(err) {
9
- if (err instanceof Error) {
10
- logger.warn(err.stack ?? err.message);
11
- return;
12
- }
13
9
  logger.warn(err);
14
10
  }
15
11
  export function setLogLevel(level) {
@@ -5,6 +5,14 @@ import {
5
5
  import { anyPass } from "remeda";
6
6
  import { CompletionItemKind, InsertTextFormat } from "vscode-languageserver-types";
7
7
  import { ast } from "../ast.js";
8
+ const STYLE_FIELDS = [
9
+ "color",
10
+ "shape",
11
+ "icon",
12
+ "border",
13
+ "opacity",
14
+ "multiple"
15
+ ].join(",");
8
16
  export class LikeC4CompletionProvider extends DefaultCompletionProvider {
9
17
  completionOptions = {
10
18
  triggerCharacters: ["."]
@@ -83,7 +91,7 @@ export class LikeC4CompletionProvider extends DefaultCompletionProvider {
83
91
  kind: CompletionItemKind.Module,
84
92
  insertTextFormat: InsertTextFormat.Snippet,
85
93
  insertText: `${keyword.value} \${1:name} \${2:*} {
86
- \${3|color,shape,border,opacity,icon|} $0
94
+ \${3|${STYLE_FIELDS}|} $0
87
95
  }`
88
96
  });
89
97
  }
@@ -94,7 +102,7 @@ export class LikeC4CompletionProvider extends DefaultCompletionProvider {
94
102
  kind: CompletionItemKind.Module,
95
103
  insertTextFormat: InsertTextFormat.Snippet,
96
104
  insertText: `${keyword.value} \${1:*} {
97
- \${2|color,shape,border,opacity,icon|} $0
105
+ \${2|${STYLE_FIELDS}|} $0
98
106
  }`
99
107
  });
100
108
  }
@@ -104,7 +112,7 @@ export class LikeC4CompletionProvider extends DefaultCompletionProvider {
104
112
  kind: CompletionItemKind.Module,
105
113
  insertTextFormat: InsertTextFormat.Snippet,
106
114
  insertText: `${keyword.value} {
107
- \${1|color,shape,border,opacity,icon|} $0
115
+ \${1|${STYLE_FIELDS}|} $0
108
116
  }`
109
117
  });
110
118
  }
@@ -1,5 +1,5 @@
1
1
  import type { Fqn } from '@likec4/core';
2
- import type { LangiumDocuments, Stream } from 'langium';
2
+ import type { DocumentCache, LangiumDocuments, Stream } from 'langium';
3
3
  import { MultiMap } from 'langium';
4
4
  import { type DeploymentAstNodeDescription, type LikeC4LangiumDocument, ast } from '../ast';
5
5
  import type { LikeC4Services } from '../module';
@@ -8,6 +8,7 @@ export declare class DeploymentsIndex {
8
8
  private services;
9
9
  protected Names: LikeC4NameProvider;
10
10
  protected langiumDocuments: LangiumDocuments;
11
+ protected documentCache: DocumentCache<string, DocumentDeploymentsIndex>;
11
12
  constructor(services: LikeC4Services);
12
13
  private documents;
13
14
  get(document: LikeC4LangiumDocument): DocumentDeploymentsIndex;
@@ -6,23 +6,16 @@ import {
6
6
  isLikeC4LangiumDocument
7
7
  } from "../ast.js";
8
8
  import { logWarnError } from "../logger.js";
9
- const DeploymentsIndexKey = Symbol.for("DeploymentsIndex");
10
9
  export class DeploymentsIndex {
11
10
  constructor(services) {
12
11
  this.services = services;
13
12
  this.Names = services.references.NameProvider;
14
13
  this.langiumDocuments = services.shared.workspace.LangiumDocuments;
15
- services.shared.workspace.DocumentBuilder.onBuildPhase(
16
- DocumentState.IndexedContent,
17
- (docs, _cancelToken) => {
18
- for (const doc of docs) {
19
- delete doc[DeploymentsIndexKey];
20
- }
21
- }
22
- );
14
+ this.documentCache = services.DocumentCache;
23
15
  }
24
16
  Names;
25
17
  langiumDocuments;
18
+ documentCache;
26
19
  documents() {
27
20
  return this.langiumDocuments.all.filter(
28
21
  (d) => isLikeC4LangiumDocument(d) && d.state >= DocumentState.IndexedContent
@@ -32,7 +25,7 @@ export class DeploymentsIndex {
32
25
  if (document.state < DocumentState.IndexedContent) {
33
26
  logWarnError(`Document ${document.uri.path} is not indexed`);
34
27
  }
35
- return document[DeploymentsIndexKey] ??= this.createDocumentIndex(document);
28
+ return this.documentCache.get(document.uri, "DeploymentsIndex", () => this.createDocumentIndex(document));
36
29
  }
37
30
  /**
38
31
  * Nested elements (nodes/artifacts) of the node
@@ -2,6 +2,7 @@ import type { Fqn } from '@likec4/core';
2
2
  import type { AstNodeDescription, LangiumDocuments, Stream } from 'langium';
3
3
  import type { ast, FqnIndexedDocument } from '../ast';
4
4
  import type { LikeC4Services } from '../module';
5
+ import { ADisposable } from '../utils';
5
6
  export interface FqnIndexEntry {
6
7
  fqn: Fqn;
7
8
  name: string;
@@ -9,7 +10,7 @@ export interface FqnIndexEntry {
9
10
  doc: FqnIndexedDocument;
10
11
  path: string;
11
12
  }
12
- export declare class FqnIndex {
13
+ export declare class FqnIndex extends ADisposable {
13
14
  private services;
14
15
  protected langiumDocuments: LangiumDocuments;
15
16
  constructor(services: LikeC4Services);
@@ -2,30 +2,37 @@ import { nameFromFqn, parentFqn } from "@likec4/core";
2
2
  import { DocumentState, DONE_RESULT, MultiMap, stream, StreamImpl } from "langium";
3
3
  import { ElementOps, isFqnIndexedDocument, isLikeC4LangiumDocument } from "../ast.js";
4
4
  import { logger, logWarnError } from "../logger.js";
5
+ import { ADisposable } from "../utils/index.js";
5
6
  import { computeDocumentFqn } from "./fqn-computation.js";
6
- export class FqnIndex {
7
+ export class FqnIndex extends ADisposable {
7
8
  constructor(services) {
9
+ super();
8
10
  this.services = services;
9
11
  this.langiumDocuments = services.shared.workspace.LangiumDocuments;
10
- services.shared.workspace.DocumentBuilder.onBuildPhase(
11
- DocumentState.IndexedContent,
12
- async (docs, _cancelToken) => {
13
- for (const doc of docs) {
14
- if (isLikeC4LangiumDocument(doc)) {
15
- delete doc.c4fqnIndex;
16
- delete doc.c4Elements;
17
- delete doc.c4Specification;
18
- delete doc.c4Relations;
19
- delete doc.c4Views;
20
- try {
21
- computeDocumentFqn(doc, services);
22
- } catch (e) {
23
- logWarnError(e);
12
+ this.onDispose(
13
+ services.shared.workspace.DocumentBuilder.onBuildPhase(
14
+ DocumentState.IndexedContent,
15
+ async (docs, _cancelToken) => {
16
+ for (const doc of docs) {
17
+ if (isLikeC4LangiumDocument(doc)) {
18
+ delete doc.c4fqnIndex;
19
+ delete doc.c4Elements;
20
+ delete doc.c4Specification;
21
+ delete doc.c4Relations;
22
+ delete doc.c4Deployments;
23
+ delete doc.c4DeploymentRelations;
24
+ delete doc.c4Globals;
25
+ delete doc.c4Views;
26
+ try {
27
+ computeDocumentFqn(doc, services);
28
+ } catch (e) {
29
+ logWarnError(e);
30
+ }
24
31
  }
25
32
  }
33
+ return await Promise.resolve();
26
34
  }
27
- return await Promise.resolve();
28
- }
35
+ )
29
36
  );
30
37
  logger.debug(`[FqnIndex] Created`);
31
38
  }
@@ -3,8 +3,9 @@ import { type ViewId } from '@likec4/core';
3
3
  import type { Cancellation, URI } from 'langium';
4
4
  import { Disposable } from 'langium';
5
5
  import type { LikeC4Services } from '../module';
6
+ import { ADisposable } from '../utils';
6
7
  type ModelParsedListener = (docs: URI[]) => void;
7
- export declare class LikeC4ModelBuilder {
8
+ export declare class LikeC4ModelBuilder extends ADisposable {
8
9
  private services;
9
10
  private langiumDocuments;
10
11
  private listeners;
@@ -15,6 +15,7 @@ import {
15
15
  flatMap,
16
16
  groupBy,
17
17
  indexBy,
18
+ isBoolean,
18
19
  isDefined,
19
20
  isEmpty,
20
21
  isNonNullish,
@@ -24,7 +25,7 @@ import {
24
25
  map,
25
26
  mapToObj,
26
27
  mapValues,
27
- pick,
28
+ omit,
28
29
  pipe,
29
30
  prop,
30
31
  reduce,
@@ -34,6 +35,7 @@ import {
34
35
  } from "remeda";
35
36
  import { isParsedLikeC4LangiumDocument } from "../ast.js";
36
37
  import { logger, logWarnError } from "../logger.js";
38
+ import { ADisposable } from "../utils/index.js";
37
39
  import { assignNavigateTo, resolveRelativePaths } from "../view-utils/index.js";
38
40
  function buildModel(services, docs) {
39
41
  const c4Specification = {
@@ -94,7 +96,8 @@ function buildModel(services, docs) {
94
96
  shape,
95
97
  icon,
96
98
  opacity,
97
- border
99
+ border,
100
+ multiple
98
101
  },
99
102
  id,
100
103
  kind,
@@ -116,6 +119,7 @@ function buildModel(services, docs) {
116
119
  opacity ??= __kind.style.opacity;
117
120
  border ??= __kind.style.border;
118
121
  technology ??= __kind.technology;
122
+ multiple ??= __kind.style.multiple;
119
123
  return {
120
124
  ...color && { color },
121
125
  ...shape && { shape },
@@ -124,6 +128,7 @@ function buildModel(services, docs) {
124
128
  ...__kind.notation && { notation: __kind.notation },
125
129
  style: {
126
130
  ...border && { border },
131
+ ...isBoolean(multiple) && { multiple },
127
132
  ...isNumber(opacity) && { opacity }
128
133
  },
129
134
  links,
@@ -403,33 +408,38 @@ function buildModel(services, docs) {
403
408
  }
404
409
  const CACHE_KEY_PARSED_MODEL = "ParsedLikeC4Model";
405
410
  const CACHE_KEY_COMPUTED_MODEL = "ComputedLikeC4Model";
406
- export class LikeC4ModelBuilder {
411
+ export class LikeC4ModelBuilder extends ADisposable {
407
412
  constructor(services) {
413
+ super();
408
414
  this.services = services;
409
415
  this.langiumDocuments = services.shared.workspace.LangiumDocuments;
410
416
  const parser = services.likec4.ModelParser;
411
- services.shared.workspace.DocumentBuilder.onUpdate((_changed, deleted) => {
412
- if (deleted.length > 0) {
413
- this.notifyListeners(deleted);
414
- }
415
- });
416
- services.shared.workspace.DocumentBuilder.onBuildPhase(
417
- DocumentState.Validated,
418
- async (docs, _cancelToken) => {
419
- let parsed = [];
420
- try {
417
+ this.onDispose(
418
+ services.shared.workspace.DocumentBuilder.onUpdate((_changed, deleted) => {
419
+ if (deleted.length > 0) {
420
+ this.notifyListeners(deleted);
421
+ }
422
+ })
423
+ );
424
+ this.onDispose(
425
+ services.shared.workspace.DocumentBuilder.onBuildPhase(
426
+ DocumentState.Validated,
427
+ async (docs, _cancelToken) => {
428
+ let parsed = [];
421
429
  logger.debug(`[ModelBuilder] onValidated (${docs.length} docs)`);
422
430
  for (const doc of docs) {
423
- parsed.push(parser.parse(doc).uri);
431
+ try {
432
+ parsed.push(parser.parse(doc).uri);
433
+ } catch (e) {
434
+ logWarnError(e);
435
+ }
436
+ }
437
+ await interruptAndCheck(_cancelToken);
438
+ if (parsed.length > 0) {
439
+ this.notifyListeners(parsed);
424
440
  }
425
- } catch (e) {
426
- logWarnError(e);
427
- }
428
- if (parsed.length > 0) {
429
- this.notifyListeners(parsed);
430
441
  }
431
- return await Promise.resolve();
432
- }
442
+ )
433
443
  );
434
444
  logger.debug(`[ModelBuilder] Created`);
435
445
  }
@@ -494,15 +504,7 @@ export class LikeC4ModelBuilder {
494
504
  });
495
505
  this.previousViews = { ...views };
496
506
  return {
497
- ...structuredClone(
498
- pick(model, [
499
- "specification",
500
- "elements",
501
- "relations",
502
- "globals",
503
- "deployments"
504
- ])
505
- ),
507
+ ...omit(model, ["views"]),
506
508
  views
507
509
  };
508
510
  });
@@ -1,5 +1,5 @@
1
1
  import { invariant } from '@likec4/core';
2
- import type { LangiumDocument } from 'langium';
2
+ import { type LangiumDocument } from 'langium';
3
3
  import type { ParsedLikeC4LangiumDocument } from '../ast';
4
4
  import type { LikeC4Services } from '../module';
5
5
  import { BaseParser } from './parser/Base';
@@ -1,5 +1,5 @@
1
1
  import { invariant } from "@likec4/core";
2
- import DefaultWeakMap from "mnemonist/default-weak-map";
2
+ import { DocumentCache, DocumentState } from "langium";
3
3
  import { pipe } from "remeda";
4
4
  import { isFqnIndexedDocument } from "../ast.js";
5
5
  import { BaseParser } from "./parser/Base.js";
@@ -27,10 +27,9 @@ export class DocumentParser extends DocumentParserFromMixins {
27
27
  export class LikeC4ModelParser {
28
28
  constructor(services) {
29
29
  this.services = services;
30
+ this.cachedParsers = new DocumentCache(services.shared, DocumentState.Validated);
30
31
  }
31
- cachedParsers = new DefaultWeakMap(
32
- (doc) => new DocumentParser(this.services, doc)
33
- );
32
+ cachedParsers;
34
33
  parse(doc) {
35
34
  invariant(isFqnIndexedDocument(doc), `Not a FqnIndexedDocument: ${doc.uri.toString(true)}`);
36
35
  try {
@@ -54,7 +53,7 @@ export class LikeC4ModelParser {
54
53
  c4Views: []
55
54
  };
56
55
  doc = Object.assign(doc, props);
57
- const parser = this.cachedParsers.get(doc);
56
+ const parser = this.forDocument(doc);
58
57
  parser.parseSpecification();
59
58
  parser.parseModel();
60
59
  parser.parseGlobals();
@@ -67,6 +66,10 @@ export class LikeC4ModelParser {
67
66
  }
68
67
  forDocument(doc) {
69
68
  invariant(isFqnIndexedDocument(doc), `Not a FqnIndexedDocument: ${doc.uri.toString(true)}`);
70
- return this.cachedParsers.get(doc);
69
+ return this.cachedParsers.get(
70
+ doc.uri,
71
+ "DocumentParser",
72
+ () => new DocumentParser(this.services, doc)
73
+ );
71
74
  }
72
75
  }
@@ -1,5 +1,5 @@
1
1
  import { invariant, nonexhaustive } from "@likec4/core";
2
- import { isDefined, isTruthy } from "remeda";
2
+ import { isBoolean, isDefined, isTruthy } from "remeda";
3
3
  import { ast, parseAstOpacityProperty, toColor } from "../../ast.js";
4
4
  import { logWarnError } from "../../logger.js";
5
5
  import { elementRef } from "../../utils/elementRef.js";
@@ -165,6 +165,12 @@ export function PredicatesParser(B) {
165
165
  }
166
166
  return acc;
167
167
  }
168
+ if (ast.isMultipleProperty(prop)) {
169
+ if (isBoolean(prop.value)) {
170
+ acc.custom[prop.key] = prop.value;
171
+ }
172
+ return acc;
173
+ }
168
174
  nonexhaustive(prop);
169
175
  },
170
176
  {
@@ -0,0 +1,8 @@
1
+ import { Disposable } from 'langium';
2
+ export declare abstract class ADisposable implements Disposable {
3
+ protected toDispose: Disposable[];
4
+ protected isDisposed: boolean;
5
+ onDispose(...disposable: Disposable[]): void;
6
+ dispose(): void;
7
+ protected throwIfDisposed(): void;
8
+ }
@@ -0,0 +1,25 @@
1
+ import { logError } from "../logger.js";
2
+ export class ADisposable {
3
+ toDispose = [];
4
+ isDisposed = false;
5
+ onDispose(...disposable) {
6
+ this.toDispose.push(...disposable);
7
+ }
8
+ dispose() {
9
+ this.throwIfDisposed();
10
+ this.isDisposed = true;
11
+ let item;
12
+ while (item = this.toDispose.pop()) {
13
+ try {
14
+ item.dispose();
15
+ } catch (e) {
16
+ logError(e);
17
+ }
18
+ }
19
+ }
20
+ throwIfDisposed() {
21
+ if (this.isDisposed) {
22
+ throw new Error("This has already been disposed");
23
+ }
24
+ }
25
+ }
@@ -1 +1,2 @@
1
+ export * from './disposable';
1
2
  export * from './stringHash';
@@ -1 +1,2 @@
1
+ export * from "./disposable.js";
1
2
  export * from "./stringHash.js";
@@ -9,13 +9,16 @@ export const RESERVED_WORDS = [
9
9
  "global"
10
10
  ];
11
11
  export function tryOrLog(fn) {
12
- return async (node, accept, cancelToken) => {
12
+ return async function tryOrLogFn(node, accept, cancelToken) {
13
13
  try {
14
14
  const result = fn(node, accept, cancelToken);
15
15
  if (isPromise(result)) {
16
16
  await result;
17
17
  }
18
+ return;
18
19
  } catch (e) {
20
+ const message = e instanceof Error ? e.message : String(e);
21
+ accept("error", `Validation failed: ${message}`, { node });
19
22
  logWarnError(e);
20
23
  }
21
24
  };
@@ -1,9 +1,9 @@
1
- import type { AstNode } from 'langium';
1
+ import { type AstNode } from 'langium';
2
2
  import { type LikeC4LangiumDocument, ast } from '../ast';
3
3
  import type { LikeC4Services } from '../module';
4
4
  type Guard<N extends AstNode> = (n: AstNode) => n is N;
5
5
  type Guarded<G> = G extends Guard<infer N> ? N : never;
6
- declare const isValidatableAstNode: (n: AstNode) => n is ast.DeployedInstance | ast.DeploymentNode | ast.DeploymentViewRulePredicate | ast.DeploymentViewRuleStyle | ast.ViewRuleAutoLayout | ast.DynamicViewGlobalPredicateRef | ast.DynamicViewIncludePredicate | ast.ViewRuleGlobalStyle | ast.ViewRuleStyle | ast.ElementDescedantsExpression | ast.ElementKindExpression | ast.ElementRef | ast.ElementTagExpression | ast.ExpandElementExpression | ast.WildcardExpression | ast.ElementPredicateWhere | ast.ElementPredicateWith | ast.ElementStringProperty | ast.ElementStyleProperty | ast.IconProperty | ast.LinkProperty | ast.MetadataBody | ast.FqnRefExpr | ast.DirectedRelationExpr | ast.InOutRelationExpr | ast.IncomingRelationExpr | ast.OutgoingRelationExpr | ast.RelationPredicateWhereV2 | ast.Element | ast.ExtendElement | ast.DeploymentView | ast.DynamicView | ast.ElementView | ast.DirectedRelationExpression | ast.InOutRelationExpression | ast.IncomingRelationExpression | ast.OutgoingRelationExpression | ast.RelationPredicateWhere | ast.RelationPredicateWith | ast.RelationStringProperty | ast.ArrowProperty | ast.ColorProperty | ast.LineProperty | ast.MetadataAttribute | ast.NotationProperty | ast.NotesProperty | ast.SpecificationElementStringProperty | ast.SpecificationRelationshipStringProperty | ast.ViewStringProperty | ast.BorderProperty | ast.OpacityProperty | ast.ShapeProperty | ast.ViewRuleGlobalPredicateRef | ast.ViewRuleGroup | ast.ExcludePredicate | ast.IncludePredicate | ast.SpecificationRelationshipKind | ast.GlobalStyle | ast.SpecificationColor | ast.NavigateToProperty | ast.DynamicViewStep | ast.Tags | ast.DeploymentRelation | ast.SpecificationDeploymentNodeKind | ast.DynamicViewParallelSteps | ast.GlobalDynamicPredicateGroup | ast.DynamicViewPredicateIterator | ast.Relation | ast.SpecificationElementKind | ast.GlobalPredicateGroup | ast.Globals | ast.GlobalStyleGroup | ast.SpecificationRule | ast.SpecificationTag;
6
+ declare const isValidatableAstNode: (n: AstNode) => n is ast.DeployedInstance | ast.DeploymentNode | ast.DeploymentViewRulePredicate | ast.DeploymentViewRuleStyle | ast.ViewRuleAutoLayout | ast.DynamicViewGlobalPredicateRef | ast.DynamicViewIncludePredicate | ast.ViewRuleGlobalStyle | ast.ViewRuleStyle | ast.ElementDescedantsExpression | ast.ElementKindExpression | ast.ElementRef | ast.ElementTagExpression | ast.ExpandElementExpression | ast.WildcardExpression | ast.ElementPredicateWhere | ast.ElementPredicateWith | ast.ElementStringProperty | ast.ElementStyleProperty | ast.IconProperty | ast.LinkProperty | ast.MetadataBody | ast.FqnRefExpr | ast.DirectedRelationExpr | ast.InOutRelationExpr | ast.IncomingRelationExpr | ast.OutgoingRelationExpr | ast.RelationPredicateWhereV2 | ast.Element | ast.ExtendElement | ast.DeploymentView | ast.DynamicView | ast.ElementView | ast.DirectedRelationExpression | ast.InOutRelationExpression | ast.IncomingRelationExpression | ast.OutgoingRelationExpression | ast.RelationPredicateWhere | ast.RelationPredicateWith | ast.RelationStringProperty | ast.ArrowProperty | ast.ColorProperty | ast.LineProperty | ast.MetadataAttribute | ast.NotationProperty | ast.NotesProperty | ast.SpecificationElementStringProperty | ast.SpecificationRelationshipStringProperty | ast.ViewStringProperty | ast.BorderProperty | ast.MultipleProperty | ast.OpacityProperty | ast.ShapeProperty | ast.ViewRuleGlobalPredicateRef | ast.ViewRuleGroup | ast.ExcludePredicate | ast.IncludePredicate | ast.SpecificationRelationshipKind | ast.GlobalStyle | ast.SpecificationColor | ast.NavigateToProperty | ast.DynamicViewStep | ast.Tags | ast.DeploymentRelation | ast.SpecificationDeploymentNodeKind | ast.DynamicViewParallelSteps | ast.GlobalDynamicPredicateGroup | ast.DynamicViewPredicateIterator | ast.Relation | ast.SpecificationElementKind | ast.GlobalPredicateGroup | ast.Globals | ast.GlobalStyleGroup | ast.SpecificationRule | ast.SpecificationTag;
7
7
  type ValidatableAstNode = Guarded<typeof isValidatableAstNode>;
8
8
  export declare function checksFromDiagnostics(doc: LikeC4LangiumDocument): {
9
9
  isValid: (n: ValidatableAstNode) => boolean;
@@ -1,3 +1,4 @@
1
+ import { DocumentState } from "langium";
1
2
  import { isNullish } from "remeda";
2
3
  import { DiagnosticSeverity } from "vscode-languageserver-types";
3
4
  import { ast } from "../ast.js";
@@ -9,6 +10,7 @@ import { elementChecks } from "./element.js";
9
10
  import { iconPropertyRuleChecks, notesPropertyRuleChecks, opacityPropertyRuleChecks } from "./property-checks.js";
10
11
  import { relationBodyChecks, relationChecks } from "./relation.js";
11
12
  import {
13
+ deploymentNodeKindChecks,
12
14
  elementKindChecks,
13
15
  globalPredicateChecks,
14
16
  globalsChecks,
@@ -86,7 +88,7 @@ const findInvalidContainer = (node) => {
86
88
  return void 0;
87
89
  };
88
90
  export function checksFromDiagnostics(doc) {
89
- const errors = doc.diagnostics?.filter((d) => d.severity === DiagnosticSeverity.Error) ?? [];
91
+ const errors = doc.state >= DocumentState.Validated ? doc.diagnostics?.filter((d) => d.severity === DiagnosticSeverity.Error) ?? [] : [];
90
92
  const invalidNodes = /* @__PURE__ */ new WeakSet();
91
93
  for (const { node } of errors) {
92
94
  if (isNullish(node) || invalidNodes.has(node)) {
@@ -109,6 +111,7 @@ export function registerValidationChecks(services) {
109
111
  const registry = services.validation.ValidationRegistry;
110
112
  registry.register({
111
113
  DeployedInstance: deployedInstanceChecks(services),
114
+ DeploymentNodeKind: deploymentNodeKindChecks(services),
112
115
  DeploymentNode: deploymentNodeChecks(services),
113
116
  DeploymentRelation: deploymentRelationChecks(services),
114
117
  FqnRefExpr: fqnRefExprChecks(services),
@@ -5,6 +5,7 @@ export declare const specificationRuleChecks: (_: LikeC4Services) => ValidationC
5
5
  export declare const modelRuleChecks: (_: LikeC4Services) => ValidationCheck<ast.Model>;
6
6
  export declare const globalsChecks: (_: LikeC4Services) => ValidationCheck<ast.Globals>;
7
7
  export declare const elementKindChecks: (services: LikeC4Services) => ValidationCheck<ast.ElementKind>;
8
+ export declare const deploymentNodeKindChecks: (services: LikeC4Services) => ValidationCheck<ast.DeploymentNodeKind>;
8
9
  export declare const tagChecks: (services: LikeC4Services) => ValidationCheck<ast.Tag>;
9
10
  export declare const relationshipChecks: (services: LikeC4Services) => ValidationCheck<ast.RelationshipKind>;
10
11
  export declare const globalPredicateChecks: (services: LikeC4Services) => ValidationCheck<ast.GlobalPredicateGroup | ast.GlobalDynamicPredicateGroup>;
@@ -61,6 +61,36 @@ export const elementKindChecks = (services) => {
61
61
  }
62
62
  });
63
63
  };
64
+ export const deploymentNodeKindChecks = (services) => {
65
+ const index = services.shared.workspace.IndexManager;
66
+ return tryOrLog((node, accept) => {
67
+ if (RESERVED_WORDS.includes(node.name)) {
68
+ accept("error", `Reserved word: ${node.name}`, {
69
+ node,
70
+ property: "name"
71
+ });
72
+ }
73
+ const sameKind = index.allElements(ast.DeploymentNodeKind).filter((n) => n.name === node.name && n.node !== node).head();
74
+ if (sameKind) {
75
+ const isAnotherDoc = sameKind.documentUri !== AstUtils.getDocument(node).uri;
76
+ accept("error", `Duplicate deploymentNode kind '${node.name}'`, {
77
+ node,
78
+ property: "name",
79
+ ...isAnotherDoc && {
80
+ relatedInformation: [
81
+ {
82
+ location: {
83
+ range: sameKind.nameSegment.range,
84
+ uri: sameKind.documentUri.toString()
85
+ },
86
+ message: `conflicting definition`
87
+ }
88
+ ]
89
+ }
90
+ });
91
+ }
92
+ });
93
+ };
64
94
  export const tagChecks = (services) => {
65
95
  const index = services.shared.workspace.IndexManager;
66
96
  return tryOrLog((node, accept) => {
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@likec4/language-server",
3
3
  "description": "LikeC4 Language Server",
4
- "version": "1.20.1",
4
+ "version": "1.20.3",
5
5
  "license": "MIT",
6
6
  "bugs": "https://github.com/likec4/likec4/issues",
7
7
  "homepage": "https://likec4.dev",
8
8
  "author": "Denis Davydkov <denis@davydkov.com>",
9
9
  "files": [
10
+ "bin",
10
11
  "dist",
11
- "contrib",
12
12
  "src",
13
13
  "!**/__mocks__/",
14
14
  "!**/__tests__/",
@@ -20,8 +20,15 @@
20
20
  "url": "git+https://github.com/likec4/likec4.git",
21
21
  "directory": "packages/language-server"
22
22
  },
23
+ "engines": {
24
+ "node": ">=20"
25
+ },
26
+ "engineStrict": true,
23
27
  "type": "module",
24
28
  "sideEffects": false,
29
+ "bin": {
30
+ "likec4-language-server": "./bin/likec4-language-server.mjs"
31
+ },
25
32
  "exports": {
26
33
  ".": {
27
34
  "sources": "./src/index.ts",
@@ -59,7 +66,8 @@
59
66
  "import": "./dist/protocol.js",
60
67
  "default": "./dist/protocol.js"
61
68
  }
62
- }
69
+ },
70
+ "./bundled": "./dist/bundled.mjs"
63
71
  },
64
72
  "publishConfig": {
65
73
  "registry": "https://registry.npmjs.org",
@@ -81,44 +89,42 @@
81
89
  "vitest:ui": "vitest --no-isolate --ui",
82
90
  "test:watch": "vitest"
83
91
  },
84
- "dependencies": {
85
- "@likec4/core": "1.20.1",
86
- "@likec4/layouts": "1.20.1",
87
- "@likec4/log": "1.20.1",
88
- "@msgpack/msgpack": "^3.0.0-beta2",
92
+ "devDependencies": {
93
+ "@likec4/core": "1.20.3",
94
+ "@likec4/icons": "1.20.3",
95
+ "@likec4/layouts": "1.20.3",
96
+ "@likec4/log": "1.20.3",
97
+ "@likec4/tsconfig": "1.20.3",
98
+ "@msgpack/msgpack": "^3.0.0-beta3",
89
99
  "@smithy/util-base64": "^3.0.0",
90
- "esm-env": "^1.2.1",
91
- "fast-equals": "^5.0.1",
100
+ "@types/node": "^20.17.7",
101
+ "@types/which": "^3.0.4",
102
+ "@vitest/coverage-v8": "^2.1.8",
103
+ "esm-env": "^1.2.2",
104
+ "execa": "^9.3.1",
105
+ "fast-equals": "^5.2.2",
92
106
  "fdir": "^6.4.2",
93
107
  "indent-string": "^5.0.0",
94
108
  "json5": "^2.2.3",
95
- "langium": "3.3.0",
109
+ "langium": "3.3.1",
110
+ "langium-cli": "3.3.0",
111
+ "natural-compare-lite": "^1.4.0",
112
+ "npm-run-all2": "^7.0.1",
96
113
  "p-debounce": "^4.0.0",
97
114
  "remeda": "^2.19.0",
98
115
  "strip-indent": "^4.0.0",
116
+ "tsx": "~4.19.2",
117
+ "turbo": "^2.3.4",
99
118
  "type-fest": "4.28.1",
119
+ "typescript": "^5.7.3",
100
120
  "ufo": "^1.5.4",
121
+ "unbuild": "^3.3.1",
122
+ "vitest": "^2.1.8",
101
123
  "vscode-jsonrpc": "8.2.0",
102
124
  "vscode-languageserver": "9.0.1",
103
125
  "vscode-languageserver-types": "3.17.5",
104
126
  "vscode-uri": "3.0.8",
105
127
  "which": "^4.0.0"
106
128
  },
107
- "devDependencies": {
108
- "@likec4/icons": "1.20.1",
109
- "@likec4/tsconfig": "1.20.1",
110
- "@types/node": "^20.17.7",
111
- "@types/which": "^3.0.4",
112
- "@vitest/coverage-v8": "^2.1.8",
113
- "execa": "^9.3.1",
114
- "langium-cli": "3.3.0",
115
- "natural-compare-lite": "^1.4.0",
116
- "npm-run-all2": "^7.0.1",
117
- "tsx": "~4.19.2",
118
- "turbo": "^2.3.3",
119
- "typescript": "^5.7.3",
120
- "unbuild": "^3.2.0",
121
- "vitest": "^2.1.8"
122
- },
123
129
  "packageManager": "yarn@4.6.0"
124
130
  }