@likec4/language-server 0.37.1 → 0.41.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 (60) hide show
  1. package/contrib/likec4.monarch.ts +5 -5
  2. package/contrib/likec4.tmLanguage.json +1 -1
  3. package/dist/Rpc.d.ts +7 -0
  4. package/dist/Rpc.js +130 -0
  5. package/dist/ast.d.ts +20 -0
  6. package/dist/ast.js +169 -141
  7. package/dist/elementRef.js +31 -44
  8. package/dist/generated/ast.d.ts +48 -7
  9. package/dist/generated/ast.js +344 -315
  10. package/dist/generated/grammar.js +2 -3177
  11. package/dist/generated/module.js +13 -18
  12. package/dist/index.js +2 -3
  13. package/dist/logger.js +39 -42
  14. package/dist/lsp/CodeLensProvider.js +28 -32
  15. package/dist/lsp/DocumentLinkProvider.js +26 -33
  16. package/dist/lsp/DocumentSymbolProvider.js +165 -167
  17. package/dist/lsp/HoverProvider.js +35 -48
  18. package/dist/lsp/SemanticTokenProvider.js +160 -201
  19. package/dist/lsp/index.js +5 -6
  20. package/dist/model/fqn-computation.js +39 -40
  21. package/dist/model/fqn-index.js +117 -141
  22. package/dist/model/index.js +5 -6
  23. package/dist/model/model-builder.d.ts +10 -5
  24. package/dist/model/model-builder.js +247 -176
  25. package/dist/model/model-locator.d.ts +1 -1
  26. package/dist/model/model-locator.js +102 -100
  27. package/dist/model/model-parser.d.ts +2 -6
  28. package/dist/model/model-parser.js +284 -286
  29. package/dist/module.d.ts +4 -1
  30. package/dist/module.js +69 -60
  31. package/dist/protocol.d.ts +16 -19
  32. package/dist/protocol.js +14 -22
  33. package/dist/references/index.js +2 -3
  34. package/dist/references/scope-computation.js +72 -69
  35. package/dist/references/scope-provider.js +126 -116
  36. package/dist/shared/WorkspaceManager.d.ts +2 -3
  37. package/dist/shared/WorkspaceManager.js +13 -16
  38. package/dist/shared/index.js +1 -2
  39. package/dist/test/index.js +1 -2
  40. package/dist/test/testServices.js +73 -61
  41. package/dist/utils.d.ts +1 -0
  42. package/dist/utils.js +4 -3
  43. package/dist/validation/element.d.ts +1 -0
  44. package/dist/validation/element.js +31 -38
  45. package/dist/validation/index.js +37 -46
  46. package/dist/validation/relation.js +46 -46
  47. package/dist/validation/specification.d.ts +1 -0
  48. package/dist/validation/specification.js +33 -30
  49. package/dist/validation/view.js +16 -22
  50. package/dist/view-utils/assignNavigateTo.d.ts +3 -0
  51. package/dist/view-utils/assignNavigateTo.js +20 -0
  52. package/dist/view-utils/index.d.ts +4 -0
  53. package/dist/view-utils/index.js +3 -0
  54. package/dist/view-utils/resolve-extended-views.d.ts +7 -0
  55. package/dist/view-utils/resolve-extended-views.js +41 -0
  56. package/dist/view-utils/resolve-relative-paths.d.ts +3 -0
  57. package/dist/view-utils/resolve-relative-paths.js +76 -0
  58. package/package.json +33 -18
  59. package/dist/registerProtocolHandlers.d.ts +0 -3
  60. package/dist/registerProtocolHandlers.js +0 -112
@@ -1,12 +1,12 @@
1
1
  // Monarch syntax highlighting for the likec4 language.
2
2
  export default {
3
3
  keywords: [
4
- 'BottomTop','LeftRight','RightLeft','TopBottom','amber','autoLayout','blue','browser','color','cylinder','description','element','exclude','extend','extends','gray','green','icon','include','indigo','it','kind','link','mobile','model','muted','of','person','primary','queue','rectangle','red','secondary','shape','sky','slate','specification','storage','style','tag','technology','this','title','view','views'
4
+ 'BottomTop','LeftRight','RightLeft','TopBottom','amber','autoLayout','blue','browser','color','crow','cylinder','dashed','description','diamond','dotted','element','exclude','extend','extends','gray','green','head','icon','include','indigo','it','kind','line','link','mobile','model','muted','none','normal','odiamond','of','onormal','open','person','primary','queue','rectangle','red','relationship','secondary','shape','sky','slate','solid','specification','storage','style','tag','tail','technology','this','title','vee','view','views'
5
5
  ],
6
6
  operators: [
7
7
  '*','.*'
8
8
  ],
9
- symbols: /\*|\.\*/,
9
+ symbols: /\*|-\[|\.\*|\]->/,
10
10
 
11
11
  tokenizer: {
12
12
  initial: [
@@ -27,17 +27,17 @@ export default {
27
27
  { regex: /:/, action: {"token":"Colon"} },
28
28
  { regex: /;/, action: {"token":"SemiColon"} },
29
29
  { regex: /,/, action: {"token":"Comma"} },
30
+ { regex: /"[^"]*"|'[^']*'/, action: {"token":"string"} },
30
31
  { regex: /((#)([^\W\d_])(((([^\W\d_])|([0-9]))|(_))|(-))*)/, action: {"token":"TagID"} },
31
32
  { regex: /((([^\W\d_])|(_))(((([^\W\d_])|([0-9]))|(_))|(-))*)/, action: { cases: { '@keywords': {"token":"keyword"}, '@default': {"token":"ID"} }} },
32
- { regex: /"[^"]*"|'[^']*'/, action: {"token":"string"} },
33
33
  { include: '@whitespace' },
34
34
  { regex: /@symbols/, action: { cases: { '@operators': {"token":"operator"}, '@default': {"token":""} }} },
35
35
  ],
36
36
  whitespace: [
37
- { regex: /[^\S\r\n]/, action: {"token":"white"} },
38
- { regex: /(([\t\r\n\v\f])|([^\S\r\n]))+/, action: {"token":"white"} },
39
37
  { regex: /\/\*/, action: {"token":"comment","next":"@comment"} },
40
38
  { regex: /\/\/[^\n\r]*/, action: {"token":"comment"} },
39
+ { regex: /(([\t\r\n\v\f])|([^\S\r\n]))+/, action: {"token":"white"} },
40
+ { regex: /[^\S\r\n]/, action: {"token":"white"} },
41
41
  ],
42
42
  comment: [
43
43
  { regex: /[^/\*]+/, action: {"token":"comment"} },
@@ -12,7 +12,7 @@
12
12
  },
13
13
  {
14
14
  "name": "keyword.control.likec4",
15
- "match": "\\b(BottomTop|LeftRight|RightLeft|TopBottom|amber|autoLayout|blue|browser|color|cylinder|description|element|exclude|extend|extends|gray|green|icon|include|indigo|it|kind|link|mobile|model|muted|of|person|primary|queue|rectangle|red|secondary|shape|sky|slate|specification|storage|style|tag|technology|this|title|view|views)\\b"
15
+ "match": "\\b(BottomTop|LeftRight|RightLeft|TopBottom|amber|autoLayout|blue|browser|color|crow|cylinder|dashed|description|diamond|dotted|element|exclude|extend|extends|gray|green|head|icon|include|indigo|it|kind|line|link|mobile|model|muted|none|normal|odiamond|of|onormal|open|person|primary|queue|rectangle|red|relationship|secondary|shape|sky|slate|solid|specification|storage|style|tag|tail|technology|this|title|vee|view|views)\\b"
16
16
  },
17
17
  {
18
18
  "name": "string.quoted.double.likec4",
package/dist/Rpc.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ import type { LikeC4Services } from './module';
2
+ export declare class Rpc {
3
+ private services;
4
+ constructor(services: LikeC4Services);
5
+ init(): void;
6
+ }
7
+ //# sourceMappingURL=Rpc.d.ts.map
package/dist/Rpc.js ADDED
@@ -0,0 +1,130 @@
1
+ import { logError, logger } from "./logger.js";
2
+ import pThrottle from "p-throttle";
3
+ import { nonexhaustive } from "@likec4/core";
4
+ import { URI } from "langium";
5
+ import { isLikeC4LangiumDocument } from "./ast.js";
6
+ import {
7
+ buildDocuments,
8
+ computeView,
9
+ fetchModel,
10
+ fetchRawModel,
11
+ locate,
12
+ onDidChangeModel,
13
+ rebuild
14
+ } from "./protocol.js";
15
+ export class Rpc {
16
+ constructor(services) {
17
+ this.services = services;
18
+ }
19
+ init() {
20
+ const { ModelBuilder: modelBuilder, ModelLocator: modelLocator } = this.services.likec4;
21
+ const connection = this.services.shared.lsp.Connection;
22
+ if (!connection) {
23
+ return;
24
+ }
25
+ logger.info(`[ServerRpc] init`);
26
+ const LangiumDocuments = this.services.shared.workspace.LangiumDocuments;
27
+ const DocumentBuilder = this.services.shared.workspace.DocumentBuilder;
28
+ const notifyClient = pThrottle({
29
+ limit: 4,
30
+ interval: 1e3
31
+ })(() => connection.sendNotification(onDidChangeModel, ""));
32
+ modelBuilder.onModelParsed(() => {
33
+ void notifyClient();
34
+ });
35
+ connection.onRequest(fetchModel, async (_cancelToken) => {
36
+ let model;
37
+ try {
38
+ model = modelBuilder.buildModel() ?? null;
39
+ } catch (e) {
40
+ model = null;
41
+ logError(e);
42
+ }
43
+ return Promise.resolve({ model });
44
+ });
45
+ connection.onRequest(fetchRawModel, async (_cancelToken) => {
46
+ let rawmodel;
47
+ try {
48
+ rawmodel = modelBuilder.buildRawModel() ?? null;
49
+ } catch (e) {
50
+ rawmodel = null;
51
+ logError(e);
52
+ }
53
+ return Promise.resolve({ rawmodel });
54
+ });
55
+ connection.onRequest(computeView, ({ viewId }) => {
56
+ return {
57
+ view: modelBuilder.computeView(viewId)
58
+ };
59
+ });
60
+ connection.onRequest(rebuild, async (cancelToken) => {
61
+ const changed = LangiumDocuments.all.map((d) => {
62
+ if (isLikeC4LangiumDocument(d)) {
63
+ delete d.c4Specification;
64
+ delete d.c4Elements;
65
+ delete d.c4Relations;
66
+ delete d.c4Views;
67
+ delete d.c4fqns;
68
+ }
69
+ return d.uri;
70
+ }).toArray();
71
+ logger.debug(`[ServerRpc] rebuild all documents: [
72
+ ${changed.map((d) => d.toString()).join("\n ")}
73
+ ]`);
74
+ await DocumentBuilder.update(changed, [], cancelToken);
75
+ return {
76
+ docs: changed.map((d) => d.toString())
77
+ };
78
+ });
79
+ connection.onRequest(buildDocuments, async ({ docs }, cancelToken) => {
80
+ if (docs.length === 0) {
81
+ logger.debug(`[ServerRpc] received empty request to rebuild`);
82
+ return;
83
+ }
84
+ logger.debug(
85
+ `[ServerRpc] received request to buildDocuments:
86
+ ${docs.map((d) => " - " + d).join("\n")}`
87
+ );
88
+ const changed = [];
89
+ for (const d of docs) {
90
+ try {
91
+ const uri = URI.parse(d);
92
+ if (LangiumDocuments.hasDocument(uri)) {
93
+ changed.push(uri);
94
+ } else {
95
+ logger.warn(`[ServerRpc] LangiumDocuments does not have document: ${d}`);
96
+ LangiumDocuments.getOrCreateDocument(uri);
97
+ }
98
+ } catch (e) {
99
+ logError(e);
100
+ }
101
+ }
102
+ if (changed.length !== docs.length) {
103
+ const all = LangiumDocuments.all.map((d) => d.uri.toString()).toArray();
104
+ logger.warn(
105
+ `
106
+ [ServerRpc] We have in LangiumDocuments: [
107
+ ${all.join("\n ")}
108
+ ]
109
+ We rebuild: [
110
+ ${changed.join("\n ")}
111
+ ]
112
+ `.trim()
113
+ );
114
+ }
115
+ await DocumentBuilder.update(changed, [], cancelToken);
116
+ });
117
+ connection.onRequest(locate, (params) => {
118
+ if ("element" in params) {
119
+ return modelLocator.locateElement(params.element, params.property ?? "name");
120
+ }
121
+ if ("relation" in params) {
122
+ return modelLocator.locateRelation(params.relation);
123
+ }
124
+ if ("view" in params) {
125
+ return modelLocator.locateView(params.view);
126
+ }
127
+ nonexhaustive(params);
128
+ });
129
+ }
130
+ }
package/dist/ast.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import { type c4 } from '@likec4/core';
2
3
  import type { LangiumDocument, MultiMap } from 'langium';
3
4
  import type { LikeC4Document } from './generated/ast';
@@ -14,6 +15,12 @@ export interface ParsedAstSpecification {
14
15
  color?: c4.ThemeColor;
15
16
  icon?: c4.IconUrl;
16
17
  }>;
18
+ relationships: Record<c4.RelationshipKind, {
19
+ color?: c4.ThemeColor;
20
+ line?: c4.RelationshipLineType;
21
+ head?: c4.RelationshipArrowType;
22
+ tail?: c4.RelationshipArrowType;
23
+ }>;
17
24
  }
18
25
  export interface ParsedAstElement {
19
26
  id: c4.Fqn;
@@ -33,6 +40,7 @@ export interface ParsedAstRelation {
33
40
  astPath: string;
34
41
  source: c4.Fqn;
35
42
  target: c4.Fqn;
43
+ kind?: c4.RelationshipKind;
36
44
  title: string;
37
45
  }
38
46
  export interface ParsedAstElementView {
@@ -93,5 +101,17 @@ export declare function toElementStyleExcludeDefaults(props?: ast.StylePropertie
93
101
  color?: "amber" | "blue" | "gray" | "slate" | "green" | "indigo" | "muted" | "red" | "secondary" | "sky";
94
102
  icon?: c4.IconUrl;
95
103
  };
104
+ export declare function toRelationshipStyle(props?: ast.SpecificationRelationshipKind['props']): {
105
+ color?: c4.ThemeColor;
106
+ line?: c4.RelationshipLineType;
107
+ head?: c4.RelationshipArrowType;
108
+ tail?: c4.RelationshipArrowType;
109
+ };
110
+ export declare function toRelationshipStyleExcludeDefaults(props?: ast.SpecificationRelationshipKind['props']): {
111
+ tail?: "none" | "onormal" | "diamond" | "odiamond" | "crow" | "open" | "vee";
112
+ head?: "none" | "onormal" | "diamond" | "odiamond" | "crow" | "open" | "vee";
113
+ line?: "solid" | "dotted";
114
+ color?: "amber" | "blue" | "slate" | "green" | "indigo" | "muted" | "primary" | "red" | "secondary" | "sky";
115
+ };
96
116
  export declare function toAutoLayout(direction: ast.ViewRuleLayoutDirection): c4.ViewRuleAutoLayout['autoLayout'];
97
117
  //# sourceMappingURL=ast.d.ts.map
package/dist/ast.js CHANGED
@@ -1,175 +1,203 @@
1
- import { DefaultElementShape, DefaultThemeColor, RelationRefError, nonexhaustive } from '@likec4/core';
2
- import { DocumentState } from 'langium';
3
- import { elementRef } from './elementRef';
4
- import * as ast from './generated/ast';
5
- import { LikeC4LanguageMetaData } from './generated/module';
1
+ import { DefaultElementShape, DefaultThemeColor, RelationRefError, nonexhaustive, DefaultLineStyle, DefaultArrowType, DefaultRelationshipColor } from "@likec4/core";
2
+ import { DocumentState } from "langium";
3
+ import { elementRef } from "./elementRef.js";
4
+ import * as ast from "./generated/ast.js";
5
+ import { LikeC4LanguageMetaData } from "./generated/module.js";
6
6
  export { ast };
7
- const idattr = Symbol.for('idattr');
7
+ const idattr = Symbol.for("idattr");
8
8
  export const ElementViewOps = {
9
- writeId(node, id) {
10
- // eslint-disable-next-line @typescript-eslint/no-explicit-any, no-extra-semi
11
- ;
12
- node[idattr] = id;
13
- return node;
14
- },
15
- readId(node) {
16
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
17
- return node[idattr];
18
- }
9
+ writeId(node, id) {
10
+ ;
11
+ node[idattr] = id;
12
+ return node;
13
+ },
14
+ readId(node) {
15
+ return node[idattr];
16
+ }
19
17
  };
20
18
  export const ElementOps = {
21
- writeId(node, id) {
22
- if (id === null) {
23
- // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-dynamic-delete
24
- delete node[idattr];
25
- delete node.fqn;
26
- }
27
- else {
28
- // eslint-disable-next-line @typescript-eslint/no-explicit-any, no-extra-semi
29
- ;
30
- node[idattr] = id;
31
- node.fqn = id;
32
- }
33
- return node;
34
- },
35
- readId(node) {
36
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
37
- return node[idattr];
19
+ writeId(node, id) {
20
+ if (id === null) {
21
+ delete node[idattr];
22
+ delete node.fqn;
23
+ } else {
24
+ ;
25
+ node[idattr] = id;
26
+ node.fqn = id;
38
27
  }
28
+ return node;
29
+ },
30
+ readId(node) {
31
+ return node[idattr];
32
+ }
39
33
  };
40
34
  export function cleanParsedModel(doc) {
41
- const specification = (doc.c4Specification = {
42
- kinds: {}
43
- });
44
- const elements = (doc.c4Elements = []);
45
- const relations = (doc.c4Relations = []);
46
- const views = (doc.c4Views = []);
47
- return {
48
- elements,
49
- relations,
50
- views,
51
- specification
52
- };
35
+ const specification = doc.c4Specification = {
36
+ kinds: {},
37
+ relationships: {}
38
+ };
39
+ const elements = doc.c4Elements = [];
40
+ const relations = doc.c4Relations = [];
41
+ const views = doc.c4Views = [];
42
+ return {
43
+ elements,
44
+ relations,
45
+ views,
46
+ specification
47
+ };
53
48
  }
54
49
  export function isLikeC4LangiumDocument(doc) {
55
- return doc.textDocument.languageId === LikeC4LanguageMetaData.languageId;
50
+ return doc.textDocument.languageId === LikeC4LanguageMetaData.languageId;
56
51
  }
57
52
  export function isParsedLikeC4LangiumDocument(doc) {
58
- return (isLikeC4LangiumDocument(doc) &&
59
- doc.state >= DocumentState.Validated &&
60
- !!doc.c4Specification &&
61
- !!doc.c4Elements &&
62
- !!doc.c4Relations &&
63
- !!doc.c4Views &&
64
- !!doc.c4fqns);
53
+ return isLikeC4LangiumDocument(doc) && doc.state >= DocumentState.Validated && !!doc.c4Specification && !!doc.c4Elements && !!doc.c4Relations && !!doc.c4Views && !!doc.c4fqns;
65
54
  }
66
55
  export const isValidLikeC4LangiumDocument = (doc) => {
67
- if (!isParsedLikeC4LangiumDocument(doc))
68
- return false;
69
- const { state, parseResult, diagnostics } = doc;
70
- return (state === DocumentState.Validated &&
71
- parseResult.lexerErrors.length === 0 &&
72
- (!diagnostics || diagnostics.every(d => d.severity !== 1)));
56
+ if (!isParsedLikeC4LangiumDocument(doc))
57
+ return false;
58
+ const { state, parseResult, diagnostics } = doc;
59
+ return state === DocumentState.Validated && parseResult.lexerErrors.length === 0 && (!diagnostics || diagnostics.every((d) => d.severity !== 1));
73
60
  };
74
61
  export function* streamModel(doc) {
75
- const elements = doc.parseResult.value.model?.elements ?? [];
76
- const traverseStack = [...elements];
77
- const relations = [];
78
- let el;
79
- while ((el = traverseStack.shift())) {
80
- if (ast.isRelation(el)) {
81
- relations.push(el);
82
- continue;
83
- }
84
- if (ast.isExtendElement(el)) {
85
- if (el.body && el.body.elements.length > 0) {
86
- traverseStack.push(...el.body.elements);
87
- }
88
- continue;
89
- }
90
- if (el.body && el.body.elements.length > 0) {
91
- for (const nested of el.body.elements) {
92
- if (ast.isRelation(nested)) {
93
- relations.push(nested);
94
- }
95
- else {
96
- traverseStack.push(nested);
97
- }
98
- }
99
- }
100
- yield el;
62
+ const elements = doc.parseResult.value.model?.elements ?? [];
63
+ const traverseStack = [...elements];
64
+ const relations = [];
65
+ let el;
66
+ while (el = traverseStack.shift()) {
67
+ if (ast.isRelation(el)) {
68
+ relations.push(el);
69
+ continue;
101
70
  }
102
- for (const relation of relations) {
103
- yield relation;
71
+ if (ast.isExtendElement(el)) {
72
+ if (el.body && el.body.elements.length > 0) {
73
+ traverseStack.push(...el.body.elements);
74
+ }
75
+ continue;
104
76
  }
105
- }
106
- export function resolveRelationPoints(node) {
107
- const target = elementRef(node.target);
108
- if (!target) {
109
- throw new RelationRefError('Invalid reference to target');
110
- }
111
- if ('source' in node) {
112
- const source = elementRef(node.source);
113
- if (!source) {
114
- throw new RelationRefError('Invalid reference to source');
77
+ if (el.body && el.body.elements.length > 0) {
78
+ for (const nested of el.body.elements) {
79
+ if (ast.isRelation(nested)) {
80
+ relations.push(nested);
81
+ } else {
82
+ traverseStack.push(nested);
115
83
  }
116
- return {
117
- source,
118
- target
119
- };
84
+ }
120
85
  }
121
- if (!ast.isElementBody(node.$container)) {
122
- throw new RelationRefError('Invalid relation parent, expected Element');
86
+ yield el;
87
+ }
88
+ for (const relation of relations) {
89
+ yield relation;
90
+ }
91
+ }
92
+ export function resolveRelationPoints(node) {
93
+ const target = elementRef(node.target);
94
+ if (!target) {
95
+ throw new RelationRefError("Invalid reference to target");
96
+ }
97
+ if ("source" in node) {
98
+ const source = elementRef(node.source);
99
+ if (!source) {
100
+ throw new RelationRefError("Invalid reference to source");
123
101
  }
124
102
  return {
125
- source: node.$container.$container,
126
- target
103
+ source,
104
+ target
127
105
  };
106
+ }
107
+ if (!ast.isElementBody(node.$container)) {
108
+ throw new RelationRefError("Invalid relation parent, expected Element");
109
+ }
110
+ return {
111
+ source: node.$container.$container,
112
+ target
113
+ };
128
114
  }
129
115
  export function toElementStyle(props) {
130
- const result = {};
131
- if (!props || props.length === 0) {
132
- return result;
116
+ const result = {};
117
+ if (!props || props.length === 0) {
118
+ return result;
119
+ }
120
+ for (const prop of props) {
121
+ if (ast.isColorProperty(prop)) {
122
+ result.color = prop.value;
123
+ continue;
133
124
  }
134
- for (const prop of props) {
135
- if (ast.isColorProperty(prop)) {
136
- result.color = prop.value;
137
- continue;
138
- }
139
- if (ast.isShapeProperty(prop)) {
140
- result.shape = prop.value;
141
- continue;
142
- }
143
- if (ast.isIconProperty(prop)) {
144
- result.icon = prop.value;
145
- continue;
146
- }
147
- nonexhaustive(prop);
125
+ if (ast.isShapeProperty(prop)) {
126
+ result.shape = prop.value;
127
+ continue;
148
128
  }
149
- return result;
129
+ if (ast.isIconProperty(prop)) {
130
+ result.icon = prop.value;
131
+ continue;
132
+ }
133
+ nonexhaustive(prop);
134
+ }
135
+ return result;
150
136
  }
151
137
  export function toElementStyleExcludeDefaults(props) {
152
- const { color, shape, ...rest } = toElementStyle(props);
153
- return {
154
- ...rest,
155
- ...(color && color !== DefaultThemeColor ? { color } : {}),
156
- ...(shape && shape !== DefaultElementShape ? { shape } : {})
157
- };
138
+ const { color, shape, ...rest } = toElementStyle(props);
139
+ return {
140
+ ...rest,
141
+ ...color && color !== DefaultThemeColor ? { color } : {},
142
+ ...shape && shape !== DefaultElementShape ? { shape } : {}
143
+ };
158
144
  }
159
- export function toAutoLayout(direction) {
160
- switch (direction) {
161
- case 'TopBottom': {
162
- return 'TB';
163
- }
164
- case 'BottomTop': {
165
- return 'BT';
145
+ export function toRelationshipStyle(props) {
146
+ const result = {};
147
+ if (!props || props.length === 0) {
148
+ return result;
149
+ }
150
+ for (const prop of props) {
151
+ if (ast.isColorProperty(prop)) {
152
+ result.color = prop.value;
153
+ continue;
154
+ }
155
+ if (ast.isLineProperty(prop)) {
156
+ result.line = prop.value;
157
+ continue;
158
+ }
159
+ if (ast.isArrowProperty(prop)) {
160
+ switch (prop.key) {
161
+ case "head": {
162
+ result.head = prop.value;
163
+ break;
166
164
  }
167
- case 'LeftRight': {
168
- return 'LR';
165
+ case "tail": {
166
+ result.tail = prop.value;
167
+ break;
169
168
  }
170
- case 'RightLeft': {
171
- return 'RL';
169
+ default: {
170
+ nonexhaustive(prop);
172
171
  }
172
+ }
173
+ continue;
174
+ }
175
+ nonexhaustive(prop);
176
+ }
177
+ return result;
178
+ }
179
+ export function toRelationshipStyleExcludeDefaults(props) {
180
+ const { color, line, head, tail } = toRelationshipStyle(props);
181
+ return {
182
+ ...color && color !== DefaultRelationshipColor ? { color } : {},
183
+ ...line && line !== DefaultLineStyle ? { line } : {},
184
+ ...head && head !== DefaultArrowType ? { head } : {},
185
+ ...tail && tail !== DefaultArrowType ? { tail } : {}
186
+ };
187
+ }
188
+ export function toAutoLayout(direction) {
189
+ switch (direction) {
190
+ case "TopBottom": {
191
+ return "TB";
192
+ }
193
+ case "BottomTop": {
194
+ return "BT";
195
+ }
196
+ case "LeftRight": {
197
+ return "LR";
198
+ }
199
+ case "RightLeft": {
200
+ return "RL";
173
201
  }
202
+ }
174
203
  }
175
- //# sourceMappingURL=ast.js.map
@@ -1,52 +1,39 @@
1
- import { invariant, nonexhaustive } from '@likec4/core';
2
- import { ast } from './ast';
1
+ import { invariant, nonexhaustive } from "@likec4/core";
2
+ import { ast } from "./ast.js";
3
3
  export function isElementRefHead(node) {
4
- if (ast.isElementRef(node)) {
5
- return !ast.isElementRef(node.$container);
6
- }
7
- if (ast.isStrictElementRef(node)) {
8
- return !ast.isStrictElementRef(node.$container);
9
- }
10
- nonexhaustive(node);
4
+ if (ast.isElementRef(node)) {
5
+ return !ast.isElementRef(node.$container);
6
+ }
7
+ if (ast.isStrictElementRef(node)) {
8
+ return !ast.isStrictElementRef(node.$container);
9
+ }
10
+ nonexhaustive(node);
11
11
  }
12
- /**
13
- * Returns referenced AST Element
14
- *
15
- */
16
12
  export function elementRef(node) {
17
- invariant(isElementRefHead(node), 'Expected head ElementRef');
18
- while (node.next) {
19
- node = node.next;
20
- }
21
- return node.el.ref;
13
+ invariant(isElementRefHead(node), "Expected head ElementRef");
14
+ while (node.next) {
15
+ node = node.next;
16
+ }
17
+ return node.el.ref;
22
18
  }
23
- /**
24
- * Returns FQN of strictElementRef
25
- * a.b.c.d - for c node returns a.b
26
- */
27
19
  export function fqnElementRef(node) {
28
- invariant(isElementRefHead(node), 'Expected head StrictElementRef');
29
- const name = [node.el.$refText];
30
- let child = node.next;
31
- while (child) {
32
- name.push(child.el.$refText);
33
- child = child.next;
34
- }
35
- return name.join('.');
20
+ invariant(isElementRefHead(node), "Expected head StrictElementRef");
21
+ const name = [node.el.$refText];
22
+ let child = node.next;
23
+ while (child) {
24
+ name.push(child.el.$refText);
25
+ child = child.next;
26
+ }
27
+ return name.join(".");
36
28
  }
37
- /**
38
- * Returns parent FQN
39
- * a.b.c.d - for c node returns a.b
40
- */
41
29
  export function parentFqnElementRef(node) {
42
- invariant(!isElementRefHead(node), 'Expected next StrictElementRef');
43
- const path = [];
44
- let parent = node.$container;
45
- while (ast.isStrictElementRef(parent)) {
46
- path.unshift(parent.el.$refText);
47
- parent = parent.$container;
48
- }
49
- invariant(path.length > 0, 'Expected non-empty parent path');
50
- return path.join('.');
30
+ invariant(!isElementRefHead(node), "Expected next StrictElementRef");
31
+ const path = [];
32
+ let parent = node.$container;
33
+ while (ast.isStrictElementRef(parent)) {
34
+ path.unshift(parent.el.$refText);
35
+ parent = parent.$container;
36
+ }
37
+ invariant(path.length > 0, "Expected non-empty parent path");
38
+ return path.join(".");
51
39
  }
52
- //# sourceMappingURL=elementRef.js.map