@likec4/language-server 1.38.0 → 1.39.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 (63) hide show
  1. package/dist/LikeC4LanguageServices.d.ts +8 -0
  2. package/dist/LikeC4LanguageServices.js +25 -4
  3. package/dist/Rpc.js +11 -4
  4. package/dist/ast.d.ts +1 -1
  5. package/dist/ast.js +2 -1
  6. package/dist/bundled.d.ts +8 -0
  7. package/dist/bundled.js +40 -0
  8. package/dist/bundled.mjs +3612 -3512
  9. package/dist/filesystem/ChokidarWatcher.js +12 -9
  10. package/dist/filesystem/LikeC4FileSystem.d.ts +0 -2
  11. package/dist/filesystem/LikeC4FileSystem.js +7 -5
  12. package/dist/filesystem/index.d.ts +7 -0
  13. package/dist/filesystem/index.js +3 -0
  14. package/dist/generated/ast.d.ts +1 -0
  15. package/dist/generated/ast.js +2 -1
  16. package/dist/generated/grammar.js +1 -1
  17. package/dist/generated-lib/icons.js +1 -1
  18. package/dist/index.d.ts +1 -0
  19. package/dist/logger.js +1 -1
  20. package/dist/mcp/MCPServerFactory.js +6 -5
  21. package/dist/mcp/server/StreamableLikeC4MCPServer.d.ts +2 -2
  22. package/dist/mcp/server/StreamableLikeC4MCPServer.js +97 -100
  23. package/dist/mcp/server/WithMCPServer.d.ts +3 -1
  24. package/dist/mcp/server/WithMCPServer.js +6 -5
  25. package/dist/mcp/tools/search-element.js +26 -11
  26. package/dist/mcp/utils.js +2 -2
  27. package/dist/model/builder/MergedSpecification.d.ts +3 -3
  28. package/dist/model/builder/MergedSpecification.js +4 -7
  29. package/dist/model/builder/assignTagColors.js +1 -1
  30. package/dist/model/builder/buildModel.d.ts +3 -8
  31. package/dist/model/builder/buildModel.js +14 -11
  32. package/dist/model/model-builder.d.ts +1 -1
  33. package/dist/model/model-locator.js +2 -1
  34. package/dist/model/model-parser.d.ts +19 -46
  35. package/dist/model/model-parser.js +13 -3
  36. package/dist/model/parser/Base.d.ts +4 -7
  37. package/dist/model/parser/Base.js +19 -0
  38. package/dist/model/parser/DeploymentModelParser.d.ts +2 -5
  39. package/dist/model/parser/DeploymentViewParser.d.ts +2 -5
  40. package/dist/model/parser/FqnRefParser.d.ts +2 -5
  41. package/dist/model/parser/GlobalsParser.d.ts +2 -5
  42. package/dist/model/parser/ImportsParser.d.ts +2 -5
  43. package/dist/model/parser/ModelParser.d.ts +2 -5
  44. package/dist/model/parser/PredicatesParser.d.ts +2 -5
  45. package/dist/model/parser/SpecificationParser.d.ts +2 -5
  46. package/dist/model/parser/ViewsParser.d.ts +2 -5
  47. package/dist/protocol.d.ts +16 -2
  48. package/dist/protocol.js +4 -0
  49. package/dist/test/testServices.d.ts +5 -1
  50. package/dist/test/testServices.js +18 -3
  51. package/dist/utils/disposable.d.ts +1 -1
  52. package/dist/utils/stringHash.js +1 -1
  53. package/dist/view-utils/resolve-relative-paths.js +1 -1
  54. package/dist/workspace/LangiumDocuments.d.ts +4 -1
  55. package/dist/workspace/LangiumDocuments.js +15 -0
  56. package/dist/workspace/ProjectsManager.d.ts +25 -11
  57. package/dist/workspace/ProjectsManager.js +85 -44
  58. package/dist/workspace/WorkspaceManager.js +5 -4
  59. package/package.json +22 -28
  60. package/dist/config/index.d.ts +0 -1
  61. package/dist/config/index.js +0 -1
  62. package/dist/config/schema.d.ts +0 -10
  63. package/dist/config/schema.js +0 -39
@@ -14,6 +14,7 @@ const searchResultSchema = z.array(
14
14
  technology: z.string().nullable(),
15
15
  shape: z.string(),
16
16
  includedInViews: includedInViewsSchema,
17
+ metadata: z.record(z.string()),
17
18
  tags: z.array(z.string())
18
19
  }),
19
20
  z.object({
@@ -26,6 +27,7 @@ const searchResultSchema = z.array(
26
27
  technology: z.string().nullable(),
27
28
  shape: z.string(),
28
29
  includedInViews: includedInViewsSchema,
30
+ metadata: z.record(z.string()),
29
31
  tags: z.array(z.string())
30
32
  })
31
33
  ])
@@ -41,21 +43,22 @@ export const searchElement = likec4Tool({
41
43
  Search LikeC4 elements and deployment nodes across all projects.
42
44
 
43
45
  Query syntax (case-insensitive):
44
- - Free text: matches id (FQN) or title
45
46
  - kind:<value>: filters by kind
46
47
  - shape:<value>: filters by shape
48
+ - meta:<key>: filters by having metadata with the given key
47
49
  - #<value>: matches assigned tags
50
+ - Free text: matches id (FQN) or title
48
51
 
49
52
  Request:
50
53
  - search: string \u2014 at least 2 characters
51
54
 
52
55
  Response (JSON object):
53
- - found: Result[] - returns top 20 results
54
56
  - total: number - total number of results
57
+ - found: Result[] - returns top 20 results
55
58
 
56
59
  Result (discriminated union by "type"):
57
- - type = "element": { id: string, name: string, kind: string, title: string, technology: string|null, shape: string, project: string, includedInViews: View[], tags: string[] }
58
- - type = "deployment-node": { id: string, name: string, kind: string, title: string, technology: string|null, shape: string, project: string, includedInViews: View[], tags: string[] }
60
+ - type = "element": { id: string, name: string, kind: string, title: string, technology: string|null, shape: string, project: string, includedInViews: View[], tags: string[], metadata: Record<string, string> }
61
+ - type = "deployment-node": { id: string, name: string, kind: string, title: string, technology: string|null, shape: string, project: string, includedInViews: View[], tags: string[], metadata: Record<string, string> }
59
62
 
60
63
  View (object) fields:
61
64
  - id: string \u2014 view identifier
@@ -68,6 +71,7 @@ Notes:
68
71
 
69
72
  Example response:
70
73
  {
74
+ "total": 1,
71
75
  "found": [
72
76
  {
73
77
  "type": "logical",
@@ -85,7 +89,8 @@ Example response:
85
89
  "type": "element"
86
90
  }
87
91
  ],
88
- "tags": ["public"]
92
+ "tags": ["public"],
93
+ "metadata": {}
89
94
  }
90
95
  ]
91
96
  }
@@ -94,8 +99,8 @@ Example response:
94
99
  search: z.string().min(2, "Search must be at least 2 characters long")
95
100
  },
96
101
  outputSchema: {
97
- found: searchResultSchema,
98
- total: z.number()
102
+ total: z.number(),
103
+ found: searchResultSchema
99
104
  }
100
105
  }, async (languageServices, args) => {
101
106
  const projects = languageServices.projects();
@@ -104,14 +109,22 @@ Example response:
104
109
  let predicate;
105
110
  if (search.startsWith("kind:")) {
106
111
  search = search.slice(5);
107
- predicate = (el) => el.kind.toLowerCase().includes(search);
112
+ logger.debug("search by kind: {search}", { search });
113
+ predicate = (el) => el.kind.toLowerCase() === search;
108
114
  } else if (search.startsWith("shape:")) {
109
115
  search = search.slice(6);
110
- predicate = (el) => el.shape.toLowerCase().includes(search);
116
+ logger.debug("search by shape: {search}", { search });
117
+ predicate = (el) => el.shape.toLowerCase() === search;
118
+ } else if (search.startsWith("meta:")) {
119
+ search = search.slice(5);
120
+ logger.debug("search by metadata: {search}", { search });
121
+ predicate = (el) => !!el.getMetadata(search);
111
122
  } else if (search.startsWith("#")) {
112
123
  search = search.slice(1);
124
+ logger.debug("search by tag: {search}", { search });
113
125
  predicate = (el) => el.tags.some((tag) => tag.toLowerCase().includes(search));
114
126
  } else {
127
+ logger.debug("search by id/title: {search}", { search });
115
128
  predicate = (el) => el.id.toLowerCase().includes(search) || el.title.toLowerCase().includes(search);
116
129
  }
117
130
  for (const project of projects) {
@@ -128,6 +141,7 @@ Example response:
128
141
  technology: el.technology,
129
142
  shape: el.shape,
130
143
  tags: [...el.tags],
144
+ metadata: el.getMetadata(),
131
145
  includedInViews: includedInViews(el.views())
132
146
  });
133
147
  }
@@ -142,6 +156,7 @@ Example response:
142
156
  technology: el.technology,
143
157
  shape: el.shape,
144
158
  tags: [...el.tags],
159
+ metadata: el.getMetadata(),
145
160
  includedInViews: includedInViews(el.views())
146
161
  });
147
162
  }
@@ -150,7 +165,7 @@ Example response:
150
165
  }
151
166
  }
152
167
  return {
153
- found: found.slice(0, 20),
154
- total: found.length
168
+ total: found.length,
169
+ found: found.slice(0, 20)
155
170
  };
156
171
  });
package/dist/mcp/utils.js CHANGED
@@ -14,7 +14,7 @@ export function likec4Tool(config, cb) {
14
14
  }
15
15
  function mkcallTool(name, languageServices, cb) {
16
16
  const tool = cb.bind(null, languageServices);
17
- return async function callTool(args, extra) {
17
+ return (async function callTool(args, extra) {
18
18
  logger.debug("Calling tool {name}, args: {args}", { name, args });
19
19
  try {
20
20
  const result = await tool.call(null, args, extra);
@@ -43,5 +43,5 @@ function mkcallTool(name, languageServices, cb) {
43
43
  isError: true
44
44
  };
45
45
  }
46
- };
46
+ });
47
47
  }
@@ -1,5 +1,5 @@
1
- import * as c4 from '@likec4/core';
2
- import { MultiMap } from '@likec4/core';
1
+ import type * as c4 from '@likec4/core';
2
+ import { MultiMap } from '@likec4/core/utils';
3
3
  import type { ParsedAstDeployment, ParsedAstDeploymentRelation, ParsedAstElement, ParsedAstRelation, ParsedAstSpecification, ParsedLikeC4LangiumDocument } from '../../ast';
4
4
  /**
5
5
  * The `MergedSpecification` class is responsible for merging multiple parsed
@@ -12,7 +12,7 @@ export declare class MergedSpecification {
12
12
  readonly tags: Readonly<Record<c4.Tag, c4.TagSpecification>>;
13
13
  readonly globals: c4.ModelGlobals;
14
14
  readonly imports: MultiMap<c4.ProjectId, c4.Fqn, Set<c4.Fqn>>;
15
- constructor(docs: ParsedLikeC4LangiumDocument[]);
15
+ constructor(docs: ReadonlyArray<ParsedLikeC4LangiumDocument>);
16
16
  /**
17
17
  * Converts a parsed model into a C4 model element.
18
18
  */
@@ -1,8 +1,5 @@
1
- import * as c4 from "@likec4/core";
2
- import { MultiMap } from "@likec4/core";
3
- import {
4
- FqnRef
5
- } from "@likec4/core/types";
1
+ import { FqnRef } from "@likec4/core/types";
2
+ import { MultiMap, nameFromFqn } from "@likec4/core/utils";
6
3
  import {
7
4
  isBoolean,
8
5
  isEmpty,
@@ -89,7 +86,7 @@ export class MergedSpecification {
89
86
  textSize ??= __kind.style.textSize;
90
87
  description ??= __kind.description;
91
88
  links ??= __kind.links;
92
- title = title === c4.nameFromFqn(id) && __kind.title ? __kind.title : title;
89
+ title = title === nameFromFqn(id) && __kind.title ? __kind.title : title;
93
90
  return {
94
91
  ...color && { color },
95
92
  ...shape && { shape },
@@ -177,7 +174,7 @@ export class MergedSpecification {
177
174
  ...rest
178
175
  } = parsed;
179
176
  description ??= __kind.description;
180
- title = title === c4.nameFromFqn(parsed.id) && __kind.title ? __kind.title : title;
177
+ title = title === nameFromFqn(parsed.id) && __kind.title ? __kind.title : title;
181
178
  return {
182
179
  ...rest,
183
180
  ...{ title },
@@ -1,4 +1,4 @@
1
- import { compareNatural, nonNullable } from "@likec4/core";
1
+ import { compareNatural, nonNullable } from "@likec4/core/utils";
2
2
  import { concat, entries, isTruthy, map, pipe, prop, pullObject, sort } from "remeda";
3
3
  export const radixColors = [
4
4
  "tomato",
@@ -1,8 +1,7 @@
1
- import * as c4 from '@likec4/core';
1
+ import type * as c4 from '@likec4/core';
2
2
  import { type MultiMap } from '@likec4/core';
3
- import type { URI } from 'langium';
4
3
  import type { ParsedLikeC4LangiumDocument } from '../../ast';
5
- import type { ProjectConfig } from '../../config';
4
+ import type { Project } from '../../workspace/ProjectsManager';
6
5
  export type BuildModelData = {
7
6
  data: c4.ParsedLikeC4ModelData;
8
7
  imports: MultiMap<c4.ProjectId, c4.Fqn, Set<c4.Fqn>>;
@@ -14,8 +13,4 @@ export type BuildModelData = {
14
13
  * This function builds a model from all documents, merging the specifications
15
14
  * and globals, and applying the extends to the elements.
16
15
  */
17
- export declare function buildModelData(project: {
18
- id: c4.ProjectId;
19
- folderUri: URI;
20
- config: Readonly<ProjectConfig>;
21
- }, docs: ParsedLikeC4LangiumDocument[]): BuildModelData;
16
+ export declare function buildModelData(project: Project, docs: ReadonlyArray<ParsedLikeC4LangiumDocument>): BuildModelData;
@@ -1,12 +1,15 @@
1
- import * as c4 from "@likec4/core";
2
1
  import {
3
2
  computeColorValues,
4
3
  isDeploymentNode,
5
- isGlobalFqn,
6
- parentFqn,
7
- sortByFqnHierarchically
4
+ isGlobalFqn
8
5
  } from "@likec4/core";
9
6
  import { resolveRulesExtendedViews } from "@likec4/core/compute-view";
7
+ import { _stage, _type, FqnRef } from "@likec4/core/types";
8
+ import {
9
+ compareNatural,
10
+ parentFqn,
11
+ sortByFqnHierarchically
12
+ } from "@likec4/core/utils";
10
13
  import {
11
14
  filter,
12
15
  flatMap,
@@ -69,7 +72,7 @@ export function buildModelData(project, docs) {
69
72
  flatMap((d) => map(d.c4Relations, c4Specification.toModelRelation)),
70
73
  filter((rel) => {
71
74
  if (!rel) return false;
72
- const source = c4.FqnRef.flatten(rel.source), target = c4.FqnRef.flatten(rel.target);
75
+ const source = FqnRef.flatten(rel.source), target = FqnRef.flatten(rel.target);
73
76
  if (isNullish(elements[source]) && !isGlobalFqn(source) || isNullish(elements[target]) && !isGlobalFqn(target)) {
74
77
  logger.debug`Invalid relation ${rel.id}
75
78
  source: ${source} resolved: ${!!elements[source]}
@@ -143,7 +146,7 @@ export function buildModelData(project, docs) {
143
146
  // model should include discriminant __
144
147
  ...model
145
148
  } = parsedAstView;
146
- if (parsedAstView[c4._type] === "element" && isNullish(title) && "viewOf" in parsedAstView) {
149
+ if (parsedAstView[_type] === "element" && isNullish(title) && "viewOf" in parsedAstView) {
147
150
  title = elements[parsedAstView.viewOf]?.title ?? null;
148
151
  }
149
152
  if (isNullish(title) && id === "index") {
@@ -151,7 +154,7 @@ export function buildModelData(project, docs) {
151
154
  }
152
155
  return {
153
156
  ...model,
154
- [c4._stage]: "parsed",
157
+ [_stage]: "parsed",
155
158
  docUri,
156
159
  description,
157
160
  title,
@@ -167,8 +170,8 @@ export function buildModelData(project, docs) {
167
170
  );
168
171
  if (!parsedViews.some((v) => v.id === "index")) {
169
172
  parsedViews.unshift({
170
- [c4._stage]: "parsed",
171
- [c4._type]: "element",
173
+ [_stage]: "parsed",
174
+ [_type]: "element",
172
175
  id: "index",
173
176
  title: "Landscape view",
174
177
  description: null,
@@ -192,7 +195,7 @@ export function buildModelData(project, docs) {
192
195
  );
193
196
  return {
194
197
  data: {
195
- [c4._stage]: "parsed",
198
+ [_stage]: "parsed",
196
199
  projectId: project.id,
197
200
  project: {
198
201
  id: project.id,
@@ -207,7 +210,7 @@ export function buildModelData(project, docs) {
207
210
  style
208
211
  })),
209
212
  deployments: c4Specification.specs.deployments,
210
- ...metadataKeys.size > 0 && { metadataKeys: [...metadataKeys].sort(c4.compareNatural) },
213
+ ...metadataKeys.size > 0 && { metadataKeys: [...metadataKeys].sort(compareNatural) },
211
214
  customColors
212
215
  },
213
216
  elements,
@@ -6,7 +6,7 @@ import { CancellationToken } from 'vscode-jsonrpc';
6
6
  import type { LikeC4Services } from '../module';
7
7
  import { ADisposable } from '../utils';
8
8
  type ModelParsedListener = (docs: URI[]) => void;
9
- export interface LikeC4ModelBuilder {
9
+ export interface LikeC4ModelBuilder extends Disposable {
10
10
  parseModel(projectId?: c4.ProjectId | undefined, cancelToken?: CancellationToken): Promise<LikeC4Model.Parsed | null>;
11
11
  unsafeSyncBuildModel(projectId: c4.ProjectId): LikeC4Model.Computed;
12
12
  buildLikeC4Model(projectId?: c4.ProjectId | undefined, cancelToken?: CancellationToken): Promise<LikeC4Model.Computed>;
@@ -1,4 +1,5 @@
1
- import { ifilter, invariant, splitGlobalFqn, toArray } from "@likec4/core";
1
+ import { splitGlobalFqn } from "@likec4/core";
2
+ import { ifilter, invariant, toArray } from "@likec4/core/utils";
2
3
  import { loggable } from "@likec4/log";
3
4
  import { AstUtils, DocumentState, GrammarUtils } from "langium";
4
5
  import { flatMap, isString, pipe } from "remeda";
@@ -1,6 +1,6 @@
1
1
  import type { ProjectId } from '@likec4/core';
2
2
  import { type LangiumDocument, type Stream } from 'langium';
3
- import type { ParsedLikeC4LangiumDocument } from '../ast';
3
+ import { type ParsedLikeC4LangiumDocument } from '../ast';
4
4
  import type { LikeC4Services } from '../module';
5
5
  import { BaseParser } from './parser/Base';
6
6
  export type ModelParsedListener = () => void;
@@ -55,11 +55,7 @@ declare const DocumentParserFromMixins: {
55
55
  isValid: import("../validation").IsValidFn;
56
56
  readonly services: LikeC4Services;
57
57
  readonly doc: ParsedLikeC4LangiumDocument;
58
- get project(): {
59
- id: ProjectId;
60
- folderUri: ProjectId;
61
- config: Readonly<import("../config").ProjectConfig>;
62
- };
58
+ get project(): import("../workspace").Project;
63
59
  resolveFqn(node: import("../generated/ast").FqnReferenceable): ProjectId;
64
60
  getAstNodePath(node: ProjectId): any;
65
61
  getMetadata(metadataAstNode: import("../generated/ast").MetadataProperty | undefined): {
@@ -75,6 +71,7 @@ declare const DocumentParserFromMixins: {
75
71
  convertLinks(source?: import("../generated/ast").LinkProperty["$container"]): ProjectId[] | undefined;
76
72
  parseLinks(source?: import("../generated/ast").LinkProperty["$container"]): ProjectId[] | undefined;
77
73
  parseIconProperty(prop: import("../generated/ast").IconProperty | undefined): ProjectId | undefined;
74
+ parseImageAlias(value: string): string | undefined;
78
75
  parseColorLiteral(astNode: import("../generated/ast").ColorLiteral): ProjectId | undefined;
79
76
  parseElementStyle(elementProps: Array<import("../generated/ast").ElementProperty> | import("../generated/ast").ElementStyleProperty | undefined): import("../ast").ParsedElementStyle;
80
77
  parseStyleProps(styleProps: Array<import("../generated/ast").StyleProperty> | undefined): import("../ast").ParsedElementStyle;
@@ -148,11 +145,7 @@ declare const DocumentParserFromMixins: {
148
145
  isValid: import("../validation").IsValidFn;
149
146
  readonly services: LikeC4Services;
150
147
  readonly doc: ParsedLikeC4LangiumDocument;
151
- get project(): {
152
- id: ProjectId;
153
- folderUri: ProjectId;
154
- config: Readonly<import("../config").ProjectConfig>;
155
- };
148
+ get project(): import("../workspace").Project;
156
149
  resolveFqn(node: import("../generated/ast").FqnReferenceable): ProjectId;
157
150
  getAstNodePath(node: ProjectId): any;
158
151
  getMetadata(metadataAstNode: import("../generated/ast").MetadataProperty | undefined): {
@@ -168,6 +161,7 @@ declare const DocumentParserFromMixins: {
168
161
  convertLinks(source?: import("../generated/ast").LinkProperty["$container"]): ProjectId[] | undefined;
169
162
  parseLinks(source?: import("../generated/ast").LinkProperty["$container"]): ProjectId[] | undefined;
170
163
  parseIconProperty(prop: import("../generated/ast").IconProperty | undefined): ProjectId | undefined;
164
+ parseImageAlias(value: string): string | undefined;
171
165
  parseColorLiteral(astNode: import("../generated/ast").ColorLiteral): ProjectId | undefined;
172
166
  parseElementStyle(elementProps: Array<import("../generated/ast").ElementProperty> | import("../generated/ast").ElementStyleProperty | undefined): import("../ast").ParsedElementStyle;
173
167
  parseStyleProps(styleProps: Array<import("../generated/ast").StyleProperty> | undefined): import("../ast").ParsedElementStyle;
@@ -203,11 +197,7 @@ declare const DocumentParserFromMixins: {
203
197
  isValid: import("../validation").IsValidFn;
204
198
  readonly services: LikeC4Services;
205
199
  readonly doc: ParsedLikeC4LangiumDocument;
206
- get project(): {
207
- id: ProjectId;
208
- folderUri: ProjectId;
209
- config: Readonly<import("../config").ProjectConfig>;
210
- };
200
+ get project(): import("../workspace").Project;
211
201
  resolveFqn(node: import("../generated/ast").FqnReferenceable): ProjectId;
212
202
  getAstNodePath(node: ProjectId): any;
213
203
  getMetadata(metadataAstNode: import("../generated/ast").MetadataProperty | undefined): {
@@ -223,6 +213,7 @@ declare const DocumentParserFromMixins: {
223
213
  convertLinks(source?: import("../generated/ast").LinkProperty["$container"]): ProjectId[] | undefined;
224
214
  parseLinks(source?: import("../generated/ast").LinkProperty["$container"]): ProjectId[] | undefined;
225
215
  parseIconProperty(prop: import("../generated/ast").IconProperty | undefined): ProjectId | undefined;
216
+ parseImageAlias(value: string): string | undefined;
226
217
  parseColorLiteral(astNode: import("../generated/ast").ColorLiteral): ProjectId | undefined;
227
218
  parseElementStyle(elementProps: Array<import("../generated/ast").ElementProperty> | import("../generated/ast").ElementStyleProperty | undefined): import("../ast").ParsedElementStyle;
228
219
  parseStyleProps(styleProps: Array<import("../generated/ast").StyleProperty> | undefined): import("../ast").ParsedElementStyle;
@@ -272,11 +263,7 @@ declare const DocumentParserFromMixins: {
272
263
  isValid: import("../validation").IsValidFn;
273
264
  readonly services: LikeC4Services;
274
265
  readonly doc: ParsedLikeC4LangiumDocument;
275
- get project(): {
276
- id: ProjectId;
277
- folderUri: ProjectId;
278
- config: Readonly<import("../config").ProjectConfig>;
279
- };
266
+ get project(): import("../workspace").Project;
280
267
  resolveFqn(node: import("../generated/ast").FqnReferenceable): ProjectId;
281
268
  getAstNodePath(node: ProjectId): any;
282
269
  getMetadata(metadataAstNode: import("../generated/ast").MetadataProperty | undefined): {
@@ -292,6 +279,7 @@ declare const DocumentParserFromMixins: {
292
279
  convertLinks(source?: import("../generated/ast").LinkProperty["$container"]): ProjectId[] | undefined;
293
280
  parseLinks(source?: import("../generated/ast").LinkProperty["$container"]): ProjectId[] | undefined;
294
281
  parseIconProperty(prop: import("../generated/ast").IconProperty | undefined): ProjectId | undefined;
282
+ parseImageAlias(value: string): string | undefined;
295
283
  parseColorLiteral(astNode: import("../generated/ast").ColorLiteral): ProjectId | undefined;
296
284
  parseElementStyle(elementProps: Array<import("../generated/ast").ElementProperty> | import("../generated/ast").ElementStyleProperty | undefined): import("../ast").ParsedElementStyle;
297
285
  parseStyleProps(styleProps: Array<import("../generated/ast").StyleProperty> | undefined): import("../ast").ParsedElementStyle;
@@ -334,11 +322,7 @@ declare const DocumentParserFromMixins: {
334
322
  isValid: import("../validation").IsValidFn;
335
323
  readonly services: LikeC4Services;
336
324
  readonly doc: ParsedLikeC4LangiumDocument;
337
- get project(): {
338
- id: ProjectId;
339
- folderUri: ProjectId;
340
- config: Readonly<import("../config").ProjectConfig>;
341
- };
325
+ get project(): import("../workspace").Project;
342
326
  resolveFqn(node: import("../generated/ast").FqnReferenceable): ProjectId;
343
327
  getAstNodePath(node: ProjectId): any;
344
328
  getMetadata(metadataAstNode: import("../generated/ast").MetadataProperty | undefined): {
@@ -354,6 +338,7 @@ declare const DocumentParserFromMixins: {
354
338
  convertLinks(source?: import("../generated/ast").LinkProperty["$container"]): ProjectId[] | undefined;
355
339
  parseLinks(source?: import("../generated/ast").LinkProperty["$container"]): ProjectId[] | undefined;
356
340
  parseIconProperty(prop: import("../generated/ast").IconProperty | undefined): ProjectId | undefined;
341
+ parseImageAlias(value: string): string | undefined;
357
342
  parseColorLiteral(astNode: import("../generated/ast").ColorLiteral): ProjectId | undefined;
358
343
  parseElementStyle(elementProps: Array<import("../generated/ast").ElementProperty> | import("../generated/ast").ElementStyleProperty | undefined): import("../ast").ParsedElementStyle;
359
344
  parseStyleProps(styleProps: Array<import("../generated/ast").StyleProperty> | undefined): import("../ast").ParsedElementStyle;
@@ -404,11 +389,7 @@ declare const DocumentParserFromMixins: {
404
389
  isValid: import("../validation").IsValidFn;
405
390
  readonly services: LikeC4Services;
406
391
  readonly doc: ParsedLikeC4LangiumDocument;
407
- get project(): {
408
- id: ProjectId;
409
- folderUri: ProjectId;
410
- config: Readonly<import("../config").ProjectConfig>;
411
- };
392
+ get project(): import("../workspace").Project;
412
393
  resolveFqn(node: import("../generated/ast").FqnReferenceable): ProjectId;
413
394
  getAstNodePath(node: ProjectId): any;
414
395
  getMetadata(metadataAstNode: import("../generated/ast").MetadataProperty | undefined): {
@@ -424,6 +405,7 @@ declare const DocumentParserFromMixins: {
424
405
  convertLinks(source?: import("../generated/ast").LinkProperty["$container"]): ProjectId[] | undefined;
425
406
  parseLinks(source?: import("../generated/ast").LinkProperty["$container"]): ProjectId[] | undefined;
426
407
  parseIconProperty(prop: import("../generated/ast").IconProperty | undefined): ProjectId | undefined;
408
+ parseImageAlias(value: string): string | undefined;
427
409
  parseColorLiteral(astNode: import("../generated/ast").ColorLiteral): ProjectId | undefined;
428
410
  parseElementStyle(elementProps: Array<import("../generated/ast").ElementProperty> | import("../generated/ast").ElementStyleProperty | undefined): import("../ast").ParsedElementStyle;
429
411
  parseStyleProps(styleProps: Array<import("../generated/ast").StyleProperty> | undefined): import("../ast").ParsedElementStyle;
@@ -467,11 +449,7 @@ declare const DocumentParserFromMixins: {
467
449
  isValid: import("../validation").IsValidFn;
468
450
  readonly services: LikeC4Services;
469
451
  readonly doc: ParsedLikeC4LangiumDocument;
470
- get project(): {
471
- id: ProjectId;
472
- folderUri: ProjectId;
473
- config: Readonly<import("../config").ProjectConfig>;
474
- };
452
+ get project(): import("../workspace").Project;
475
453
  resolveFqn(node: import("../generated/ast").FqnReferenceable): ProjectId;
476
454
  getAstNodePath(node: ProjectId): any;
477
455
  getMetadata(metadataAstNode: import("../generated/ast").MetadataProperty | undefined): {
@@ -487,6 +465,7 @@ declare const DocumentParserFromMixins: {
487
465
  convertLinks(source?: import("../generated/ast").LinkProperty["$container"]): ProjectId[] | undefined;
488
466
  parseLinks(source?: import("../generated/ast").LinkProperty["$container"]): ProjectId[] | undefined;
489
467
  parseIconProperty(prop: import("../generated/ast").IconProperty | undefined): ProjectId | undefined;
468
+ parseImageAlias(value: string): string | undefined;
490
469
  parseColorLiteral(astNode: import("../generated/ast").ColorLiteral): ProjectId | undefined;
491
470
  parseElementStyle(elementProps: Array<import("../generated/ast").ElementProperty> | import("../generated/ast").ElementStyleProperty | undefined): import("../ast").ParsedElementStyle;
492
471
  parseStyleProps(styleProps: Array<import("../generated/ast").StyleProperty> | undefined): import("../ast").ParsedElementStyle;
@@ -510,11 +489,7 @@ declare const DocumentParserFromMixins: {
510
489
  isValid: import("../validation").IsValidFn;
511
490
  readonly services: LikeC4Services;
512
491
  readonly doc: ParsedLikeC4LangiumDocument;
513
- get project(): {
514
- id: ProjectId;
515
- folderUri: ProjectId;
516
- config: Readonly<import("../config").ProjectConfig>;
517
- };
492
+ get project(): import("../workspace").Project;
518
493
  resolveFqn(node: import("../generated/ast").FqnReferenceable): ProjectId;
519
494
  getAstNodePath(node: ProjectId): any;
520
495
  getMetadata(metadataAstNode: import("../generated/ast").MetadataProperty | undefined): {
@@ -530,6 +505,7 @@ declare const DocumentParserFromMixins: {
530
505
  convertLinks(source?: import("../generated/ast").LinkProperty["$container"]): ProjectId[] | undefined;
531
506
  parseLinks(source?: import("../generated/ast").LinkProperty["$container"]): ProjectId[] | undefined;
532
507
  parseIconProperty(prop: import("../generated/ast").IconProperty | undefined): ProjectId | undefined;
508
+ parseImageAlias(value: string): string | undefined;
533
509
  parseColorLiteral(astNode: import("../generated/ast").ColorLiteral): ProjectId | undefined;
534
510
  parseElementStyle(elementProps: Array<import("../generated/ast").ElementProperty> | import("../generated/ast").ElementStyleProperty | undefined): import("../ast").ParsedElementStyle;
535
511
  parseStyleProps(styleProps: Array<import("../generated/ast").StyleProperty> | undefined): import("../ast").ParsedElementStyle;
@@ -568,11 +544,7 @@ declare const DocumentParserFromMixins: {
568
544
  isValid: import("../validation").IsValidFn;
569
545
  readonly services: LikeC4Services;
570
546
  readonly doc: ParsedLikeC4LangiumDocument;
571
- get project(): {
572
- id: ProjectId;
573
- folderUri: ProjectId;
574
- config: Readonly<import("../config").ProjectConfig>;
575
- };
547
+ get project(): import("../workspace").Project;
576
548
  resolveFqn(node: import("../generated/ast").FqnReferenceable): ProjectId;
577
549
  getAstNodePath(node: ProjectId): any;
578
550
  getMetadata(metadataAstNode: import("../generated/ast").MetadataProperty | undefined): {
@@ -588,6 +560,7 @@ declare const DocumentParserFromMixins: {
588
560
  convertLinks(source?: import("../generated/ast").LinkProperty["$container"]): ProjectId[] | undefined;
589
561
  parseLinks(source?: import("../generated/ast").LinkProperty["$container"]): ProjectId[] | undefined;
590
562
  parseIconProperty(prop: import("../generated/ast").IconProperty | undefined): ProjectId | undefined;
563
+ parseImageAlias(value: string): string | undefined;
591
564
  parseColorLiteral(astNode: import("../generated/ast").ColorLiteral): ProjectId | undefined;
592
565
  parseElementStyle(elementProps: Array<import("../generated/ast").ElementProperty> | import("../generated/ast").ElementStyleProperty | undefined): import("../ast").ParsedElementStyle;
593
566
  parseStyleProps(styleProps: Array<import("../generated/ast").StyleProperty> | undefined): import("../ast").ParsedElementStyle;
@@ -1,8 +1,9 @@
1
- import { DefaultWeakMap, MultiMap } from "@likec4/core/utils";
1
+ import { DefaultWeakMap, invariant, MultiMap } from "@likec4/core/utils";
2
2
  import { loggable } from "@likec4/log";
3
3
  import { DocumentState } from "langium";
4
4
  import { pipe } from "remeda";
5
5
  import { DiagnosticSeverity } from "vscode-languageserver-types";
6
+ import { isLikeC4LangiumDocument } from "../ast.js";
6
7
  import { isLikeC4Builtin } from "../likec4lib.js";
7
8
  import { logger as rootLogger } from "../logger.js";
8
9
  import { BaseParser } from "./parser/Base.js";
@@ -73,11 +74,20 @@ export class LikeC4ModelParser {
73
74
  }
74
75
  forDocument(doc) {
75
76
  if (doc.state < DocumentState.Linked) {
76
- logger.warn(`Document ${doc.uri.toString()} is not linked`);
77
+ logger.warn(`Document {doc} is not linked`, { doc: doc.uri.toString() });
77
78
  }
78
79
  return this.cachedParsers.get(doc);
79
80
  }
80
81
  createParser(doc) {
82
+ invariant(isLikeC4LangiumDocument(doc), `Document ${doc.uri.toString()} is not a LikeC4 document`);
83
+ if (doc.likec4ProjectId) {
84
+ logger.debug(`create parser {projectId} document {doc}`, {
85
+ projectId: doc.likec4ProjectId,
86
+ doc: doc.uri.toString()
87
+ });
88
+ } else {
89
+ logger.warn(`create parser for document without project {doc}`, { doc: doc.uri.toString() });
90
+ }
81
91
  const props = {
82
92
  c4Specification: {
83
93
  tags: {},
@@ -110,7 +120,7 @@ export class LikeC4ModelParser {
110
120
  parser.parseDeployment();
111
121
  parser.parseViews();
112
122
  } catch (e) {
113
- logger.error(`Error parsing document ${doc.uri.toString()}`, { cause: e });
123
+ logger.error(`Error parsing document {doc}`, { doc: doc.uri.toString(), error: e });
114
124
  }
115
125
  return parser;
116
126
  }
@@ -1,10 +1,10 @@
1
1
  import type * as c4 from '@likec4/core';
2
2
  import { type MarkdownOrString } from '@likec4/core';
3
- import type { AstNode, URI } from 'langium';
3
+ import type { AstNode } from 'langium';
4
4
  import { type ParsedElementStyle, type ParsedLikeC4LangiumDocument, ast } from '../../ast';
5
- import type { ProjectConfig } from '../../config';
6
5
  import type { LikeC4Services } from '../../module';
7
6
  import { type IsValidFn } from '../../validation';
7
+ import type { Project } from '../../workspace/ProjectsManager';
8
8
  export type GConstructor<T = {}> = new (...args: any[]) => T;
9
9
  export declare function toSingleLine(str: string): string;
10
10
  export declare function toSingleLine(str: string | undefined | null): string | undefined;
@@ -23,11 +23,7 @@ export declare class BaseParser {
23
23
  readonly doc: ParsedLikeC4LangiumDocument;
24
24
  isValid: IsValidFn;
25
25
  constructor(services: LikeC4Services, doc: ParsedLikeC4LangiumDocument);
26
- get project(): {
27
- id: c4.ProjectId;
28
- folderUri: URI;
29
- config: Readonly<ProjectConfig>;
30
- };
26
+ get project(): Project;
31
27
  resolveFqn(node: ast.FqnReferenceable): c4.Fqn;
32
28
  getAstNodePath(node: AstNode): any;
33
29
  getMetadata(metadataAstNode: ast.MetadataProperty | undefined): {
@@ -43,6 +39,7 @@ export declare class BaseParser {
43
39
  convertLinks(source?: ast.LinkProperty['$container']): c4.Link[] | undefined;
44
40
  parseLinks(source?: ast.LinkProperty['$container']): c4.Link[] | undefined;
45
41
  parseIconProperty(prop: ast.IconProperty | undefined): c4.IconUrl | undefined;
42
+ parseImageAlias(value: string): string | undefined;
46
43
  parseColorLiteral(astNode: ast.ColorLiteral): c4.ColorLiteral | undefined;
47
44
  parseElementStyle(elementProps: Array<ast.ElementProperty> | ast.ElementStyleProperty | undefined): ParsedElementStyle;
48
45
  parseStyleProps(styleProps: Array<ast.StyleProperty> | undefined): ParsedElementStyle;
@@ -23,6 +23,7 @@ import {
23
23
  parseMarkdownAsString,
24
24
  toColor
25
25
  } from "../../ast.js";
26
+ import { logger } from "../../logger.js";
26
27
  import { projectIdFrom } from "../../utils/index.js";
27
28
  import { readStrictFqn } from "../../utils/elementRef.js";
28
29
  import { checksFromDiagnostics } from "../../validation/index.js";
@@ -179,10 +180,14 @@ export class BaseParser {
179
180
  }
180
181
  case (value && hasProtocol(value)): {
181
182
  if (value.startsWith("file:")) {
183
+ logger.warn(`Icon property '${value}' used the 'file' protocol which is not supported`);
182
184
  return void 0;
183
185
  }
184
186
  return value;
185
187
  }
188
+ case (value && value.startsWith("@")): {
189
+ return this.parseImageAlias(value);
190
+ }
186
191
  case (value && isRelative(value)): {
187
192
  return joinRelativeURL(this.doc.uri.toString(), "../", value);
188
193
  }
@@ -190,10 +195,24 @@ export class BaseParser {
190
195
  return joinURL(this.project.folderUri.toString(), value);
191
196
  }
192
197
  default: {
198
+ logger.warn(`Icon property '${value}' is not a valid URL, library icon, image alias or 'none'`);
193
199
  return void 0;
194
200
  }
195
201
  }
196
202
  }
203
+ parseImageAlias(value) {
204
+ const slashIndex = value.indexOf("/");
205
+ const aliasName = slashIndex > 0 ? value.substring(0, slashIndex) : value;
206
+ const remainingPath = slashIndex > 0 ? value.substring(slashIndex + 1) : "";
207
+ const imageAliases = { "@": "./images", ...this.project.config.imageAliases || {} };
208
+ const aliasPath = imageAliases[aliasName];
209
+ if (!aliasPath) {
210
+ logger.warn(`Image alias "${aliasName}" not found in project configuration`);
211
+ return void 0;
212
+ }
213
+ const fullPath = remainingPath ? joinURL(aliasPath, remainingPath) : aliasPath;
214
+ return joinURL(this.project.folderUri.toString(), fullPath);
215
+ }
197
216
  parseColorLiteral(astNode) {
198
217
  if (!this.isValid(astNode)) {
199
218
  return void 0;
@@ -30,11 +30,7 @@ export declare function DeploymentModelParser<TBase extends WithExpressionV2>(B:
30
30
  isValid: import("../../validation").IsValidFn;
31
31
  readonly services: import("../..").LikeC4Services;
32
32
  readonly doc: import("../../ast").ParsedLikeC4LangiumDocument;
33
- get project(): {
34
- id: c4.ProjectId;
35
- folderUri: c4;
36
- config: Readonly<import("../../config").ProjectConfig>;
37
- };
33
+ get project(): import("../../workspace").Project;
38
34
  resolveFqn(node: ast.FqnReferenceable): c4.Fqn;
39
35
  getAstNodePath(node: c4): any;
40
36
  getMetadata(metadataAstNode: ast.MetadataProperty | undefined): {
@@ -50,6 +46,7 @@ export declare function DeploymentModelParser<TBase extends WithExpressionV2>(B:
50
46
  convertLinks(source?: ast.LinkProperty["$container"]): c4.Link[] | undefined;
51
47
  parseLinks(source?: ast.LinkProperty["$container"]): c4.Link[] | undefined;
52
48
  parseIconProperty(prop: ast.IconProperty | undefined): c4.IconUrl | undefined;
49
+ parseImageAlias(value: string): string | undefined;
53
50
  parseColorLiteral(astNode: ast.ColorLiteral): c4.ColorLiteral | undefined;
54
51
  parseElementStyle(elementProps: Array<ast.ElementProperty> | ast.ElementStyleProperty | undefined): import("../../ast").ParsedElementStyle;
55
52
  parseStyleProps(styleProps: Array<ast.StyleProperty> | undefined): import("../../ast").ParsedElementStyle;