@likec4/language-server 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/contrib/likec4.monarch.ts +4 -4
- package/contrib/likec4.tmLanguage.json +1 -1
- package/dist/ast.d.ts +28 -11
- package/dist/ast.js +14 -13
- package/dist/generated/ast.d.ts +75 -19
- package/dist/generated/ast.js +96 -7
- package/dist/generated/grammar.js +1 -1
- package/dist/lsp/CodeLensProvider.js +5 -2
- package/dist/lsp/DocumentSymbolProvider.d.ts +1 -1
- package/dist/lsp/DocumentSymbolProvider.js +14 -9
- package/dist/lsp/SemanticTokenProvider.js +1 -1
- package/dist/model/model-builder.js +68 -45
- package/dist/model/model-locator.d.ts +2 -2
- package/dist/model/model-locator.js +12 -16
- package/dist/model/model-parser.d.ts +2 -0
- package/dist/model/model-parser.js +140 -30
- package/dist/model-change/ModelChanges.d.ts +2 -1
- package/dist/model-change/ModelChanges.js +39 -35
- package/dist/model-change/changeElementStyle.d.ts +18 -0
- package/dist/model-change/changeElementStyle.js +141 -0
- package/dist/model-change/changeViewLayout.d.ts +4 -4
- package/dist/model-change/changeViewLayout.js +4 -5
- package/dist/protocol.d.ts +12 -12
- package/dist/references/scope-computation.js +36 -31
- package/dist/shared/NodeKindProvider.js +4 -2
- package/dist/validation/dynamic-view-rule.d.ts +5 -0
- package/dist/validation/dynamic-view-rule.js +32 -0
- package/dist/validation/dynamic-view-step.d.ts +5 -0
- package/dist/validation/dynamic-view-step.js +33 -0
- package/dist/validation/index.js +5 -1
- package/dist/validation/view-predicates/expanded-element.js +1 -0
- package/dist/validation/view-predicates/outgoing.js +2 -2
- package/dist/validation/view.d.ts +1 -1
- package/dist/validation/view.js +1 -3
- package/dist/view-utils/assignNavigateTo.d.ts +1 -1
- package/dist/view-utils/assignNavigateTo.js +2 -1
- package/dist/view-utils/resolve-extended-views.d.ts +2 -2
- package/dist/view-utils/resolve-relative-paths.d.ts +2 -2
- package/dist/view-utils/resolve-relative-paths.js +2 -3
- package/package.json +6 -6
- package/dist/model-change/changeViewStyle.d.ts +0 -15
- package/dist/model-change/changeViewStyle.js +0 -123
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type Fqn, type NonEmptyArray } from '@likec4/core';
|
|
2
|
+
import { type Range, TextEdit } from 'vscode-languageserver-protocol';
|
|
3
|
+
import { ast, type ParsedAstView, type ParsedLikeC4LangiumDocument } from '../ast';
|
|
4
|
+
import type { LikeC4Services } from '../module';
|
|
5
|
+
import type { ChangeView } from '../protocol';
|
|
6
|
+
type ChangeElementStyleArg = {
|
|
7
|
+
view: ParsedAstView;
|
|
8
|
+
doc: ParsedLikeC4LangiumDocument;
|
|
9
|
+
viewAst: ast.LikeC4View;
|
|
10
|
+
targets: NonEmptyArray<Fqn>;
|
|
11
|
+
style: ChangeView.ChangeElementStyle['style'];
|
|
12
|
+
};
|
|
13
|
+
export declare function changeElementStyle(services: LikeC4Services, { view, viewAst, targets, style }: ChangeElementStyleArg): {
|
|
14
|
+
modifiedRange: Range;
|
|
15
|
+
edits: TextEdit[];
|
|
16
|
+
};
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=changeElementStyle.d.ts.map
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { invariant, isAncestor, nonNullable } from "@likec4/core";
|
|
2
|
+
import { GrammarUtils } from "langium";
|
|
3
|
+
import { entries, filter, findLast, last } from "remeda";
|
|
4
|
+
import { TextEdit } from "vscode-languageserver-protocol";
|
|
5
|
+
import { ast } from "../ast.js";
|
|
6
|
+
const { findNodeForKeyword, findNodeForProperty } = GrammarUtils;
|
|
7
|
+
const asViewStyleRule = (target, style, indent = 0) => {
|
|
8
|
+
const indentStr = indent > 0 ? " ".repeat(indent) : "";
|
|
9
|
+
return [
|
|
10
|
+
indentStr + `style ${target} {`,
|
|
11
|
+
...entries.strict(style).map(
|
|
12
|
+
([key, value]) => indentStr + ` ${key} ${key === "opacity" ? value.toString() + "%" : value}`
|
|
13
|
+
),
|
|
14
|
+
indentStr + `}`
|
|
15
|
+
];
|
|
16
|
+
};
|
|
17
|
+
const isMatchingViewRule = (fqn, index) => (rule) => {
|
|
18
|
+
if (!ast.isViewRuleStyle(rule)) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
const [target, ...rest] = rule.targets;
|
|
22
|
+
if (!target || rest.length > 0 || !ast.isElementRef(target)) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
const ref = target.el.ref;
|
|
26
|
+
const _fqn = ref ? index.getFqn(ref) : null;
|
|
27
|
+
return _fqn === fqn;
|
|
28
|
+
};
|
|
29
|
+
export function changeElementStyle(services, {
|
|
30
|
+
view,
|
|
31
|
+
viewAst,
|
|
32
|
+
targets,
|
|
33
|
+
style
|
|
34
|
+
}) {
|
|
35
|
+
const viewCstNode = viewAst.$cstNode;
|
|
36
|
+
invariant(viewCstNode, "viewCstNode");
|
|
37
|
+
const insertPos = last(viewAst.body.rules)?.$cstNode?.range.end ?? viewAst.body.$cstNode?.range.end;
|
|
38
|
+
invariant(insertPos, "insertPos is not defined");
|
|
39
|
+
const indent = viewCstNode.range.start.character + 2;
|
|
40
|
+
const fqnIndex = services.likec4.FqnIndex;
|
|
41
|
+
const styleRules = filter(viewAst.body.rules, ast.isViewRuleStyle);
|
|
42
|
+
const viewOf = view.__ === "element" ? view.viewOf : null;
|
|
43
|
+
const existing = [];
|
|
44
|
+
const insert = [];
|
|
45
|
+
targets.forEach((target) => {
|
|
46
|
+
const rule = findLast(styleRules, isMatchingViewRule(target, fqnIndex));
|
|
47
|
+
const fqn = viewOf && isAncestor(viewOf, target) ? target.substring(viewOf.length + 1) : target;
|
|
48
|
+
if (rule) {
|
|
49
|
+
existing.push({ fqn, rule });
|
|
50
|
+
} else {
|
|
51
|
+
insert.push({ fqn });
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
const modifiedRange = {
|
|
55
|
+
start: insertPos,
|
|
56
|
+
end: insertPos
|
|
57
|
+
};
|
|
58
|
+
const includeRange = (range) => {
|
|
59
|
+
if (range.start.line <= modifiedRange.start.line) {
|
|
60
|
+
if (range.start.line == modifiedRange.start.line) {
|
|
61
|
+
modifiedRange.start.character = Math.min(range.start.character, modifiedRange.start.character);
|
|
62
|
+
} else {
|
|
63
|
+
modifiedRange.start = range.start;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (range.end.line >= modifiedRange.end.line) {
|
|
67
|
+
if (range.end.line == modifiedRange.end.line) {
|
|
68
|
+
modifiedRange.end.character = Math.max(range.end.character, modifiedRange.end.character);
|
|
69
|
+
} else {
|
|
70
|
+
modifiedRange.end = range.end;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
const edits = [];
|
|
75
|
+
if (insert.length > 0) {
|
|
76
|
+
const linesToInsert = insert.flatMap(({ fqn }) => asViewStyleRule(fqn, style, indent));
|
|
77
|
+
edits.push(
|
|
78
|
+
TextEdit.insert(
|
|
79
|
+
insertPos,
|
|
80
|
+
"\n" + linesToInsert.join("\n")
|
|
81
|
+
)
|
|
82
|
+
);
|
|
83
|
+
modifiedRange.start = {
|
|
84
|
+
line: insertPos.line + 1,
|
|
85
|
+
character: indent + 1
|
|
86
|
+
};
|
|
87
|
+
modifiedRange.end = {
|
|
88
|
+
line: insertPos.line + linesToInsert.length,
|
|
89
|
+
character: last(linesToInsert)?.length ?? 0
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
if (existing.length > 0) {
|
|
93
|
+
for (const { rule } of existing) {
|
|
94
|
+
const ruleCstNode = rule.$cstNode;
|
|
95
|
+
invariant(ruleCstNode, "RuleCstNode not found");
|
|
96
|
+
for (const [key, _value] of entries.strict(style)) {
|
|
97
|
+
const value = key === "opacity" ? _value.toString() + "%" : _value;
|
|
98
|
+
const ruleProp = rule.styleprops.find((p) => p.key === key);
|
|
99
|
+
if (ruleProp && ruleProp.$cstNode) {
|
|
100
|
+
const { range: { start, end } } = nonNullable(
|
|
101
|
+
findNodeForProperty(ruleProp.$cstNode, "value"),
|
|
102
|
+
"cant find value cst node"
|
|
103
|
+
);
|
|
104
|
+
includeRange({
|
|
105
|
+
start,
|
|
106
|
+
end: {
|
|
107
|
+
line: start.line,
|
|
108
|
+
character: start.character + value.length
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
edits.push(TextEdit.replace({ start, end }, value));
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const insertPos2 = findNodeForKeyword(ruleCstNode, "{")?.range.end;
|
|
115
|
+
invariant(insertPos2, "Opening brace not found");
|
|
116
|
+
const indentStr = " ".repeat(2 + ruleCstNode.range.start.character);
|
|
117
|
+
const insertKeyValue = indentStr + key + " " + value;
|
|
118
|
+
edits.push(
|
|
119
|
+
TextEdit.insert(
|
|
120
|
+
insertPos2,
|
|
121
|
+
"\n" + insertKeyValue
|
|
122
|
+
)
|
|
123
|
+
);
|
|
124
|
+
includeRange({
|
|
125
|
+
start: {
|
|
126
|
+
line: insertPos2.line + 1,
|
|
127
|
+
character: indentStr.length
|
|
128
|
+
},
|
|
129
|
+
end: {
|
|
130
|
+
line: insertPos2.line + 1,
|
|
131
|
+
character: insertKeyValue.length
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
modifiedRange,
|
|
139
|
+
edits
|
|
140
|
+
};
|
|
141
|
+
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { type AutoLayoutDirection } from '@likec4/core';
|
|
2
2
|
import { TextEdit } from 'vscode-languageserver-protocol';
|
|
3
|
-
import { ast, type
|
|
3
|
+
import { ast, type ParsedAstView, type ParsedLikeC4LangiumDocument } from '../ast';
|
|
4
4
|
import type { LikeC4Services } from '../module';
|
|
5
5
|
type ChangeViewLayoutArg = {
|
|
6
|
-
view:
|
|
6
|
+
view: ParsedAstView;
|
|
7
7
|
doc: ParsedLikeC4LangiumDocument;
|
|
8
|
-
viewAst: ast.
|
|
8
|
+
viewAst: ast.LikeC4View;
|
|
9
9
|
layout: AutoLayoutDirection;
|
|
10
10
|
};
|
|
11
|
-
export declare function changeViewLayout(services: LikeC4Services, {
|
|
11
|
+
export declare function changeViewLayout(services: LikeC4Services, { viewAst, layout }: ChangeViewLayoutArg): TextEdit;
|
|
12
12
|
export {};
|
|
13
13
|
//# sourceMappingURL=changeViewLayout.d.ts.map
|
|
@@ -5,7 +5,6 @@ import { TextEdit } from "vscode-languageserver-protocol";
|
|
|
5
5
|
import { ast, toAstViewLayoutDirection } from "../ast.js";
|
|
6
6
|
const { findNodeForProperty } = GrammarUtils;
|
|
7
7
|
export function changeViewLayout(services, {
|
|
8
|
-
view,
|
|
9
8
|
viewAst,
|
|
10
9
|
layout
|
|
11
10
|
}) {
|
|
@@ -16,15 +15,15 @@ export function changeViewLayout(services, {
|
|
|
16
15
|
if (existingRule && existingRule.$cstNode) {
|
|
17
16
|
const directionCstNode = findNodeForProperty(existingRule.$cstNode, "direction");
|
|
18
17
|
if (directionCstNode) {
|
|
19
|
-
return
|
|
18
|
+
return TextEdit.replace(directionCstNode.range, newlayout);
|
|
20
19
|
}
|
|
21
|
-
return
|
|
20
|
+
return TextEdit.replace(existingRule.$cstNode.range, `autoLayout ${newlayout}`);
|
|
22
21
|
}
|
|
23
|
-
const insertPos = last(viewAst.body.rules)?.$cstNode?.range.end ??
|
|
22
|
+
const insertPos = last(viewAst.body.rules)?.$cstNode?.range.end ?? viewAst.body.$cstNode?.range.end;
|
|
24
23
|
invariant(insertPos, "insertPos is not defined");
|
|
25
24
|
const indent = " ".repeat(2 + viewCstNode.range.start.character);
|
|
26
25
|
const insert = `
|
|
27
26
|
|
|
28
27
|
${indent}autoLayout ${newlayout}`;
|
|
29
|
-
return
|
|
28
|
+
return TextEdit.insert(insertPos, insert);
|
|
30
29
|
}
|
package/dist/protocol.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AutoLayoutDirection, ComputedView, ElementShape, Fqn, LikeC4ComputedModel, LikeC4Model, NonEmptyArray, RelationID, ThemeColor, ViewID } from '@likec4/core';
|
|
1
|
+
import type { AutoLayoutDirection, BorderStyle, ComputedView, ElementShape, Fqn, LikeC4ComputedModel, LikeC4Model, NonEmptyArray, RelationID, ThemeColor, ViewID } from '@likec4/core';
|
|
2
2
|
import type { DocumentUri, Location } from 'vscode-languageserver-protocol';
|
|
3
3
|
import { NotificationType, RequestType, RequestType0 } from 'vscode-languageserver-protocol';
|
|
4
4
|
export declare const onDidChangeModel: NotificationType<string>;
|
|
@@ -33,22 +33,22 @@ export type LocateParams = {
|
|
|
33
33
|
export declare const locate: RequestType<LocateParams, Location | null, void>;
|
|
34
34
|
export type LocateRequest = typeof locate;
|
|
35
35
|
export declare namespace ChangeView {
|
|
36
|
-
interface ChangeColor {
|
|
37
|
-
op: 'change-color';
|
|
38
|
-
color: ThemeColor;
|
|
39
|
-
targets: NonEmptyArray<Fqn>;
|
|
40
|
-
}
|
|
41
|
-
interface ChangeShape {
|
|
42
|
-
op: 'change-shape';
|
|
43
|
-
shape: ElementShape;
|
|
44
|
-
targets: NonEmptyArray<Fqn>;
|
|
45
|
-
}
|
|
46
36
|
interface ChangeAutoLayout {
|
|
47
37
|
op: 'change-autolayout';
|
|
48
38
|
layout: AutoLayoutDirection;
|
|
49
39
|
}
|
|
40
|
+
interface ChangeElementStyle {
|
|
41
|
+
op: 'change-element-style';
|
|
42
|
+
style: {
|
|
43
|
+
border?: BorderStyle;
|
|
44
|
+
opacity?: number;
|
|
45
|
+
shape?: ElementShape;
|
|
46
|
+
color?: ThemeColor;
|
|
47
|
+
};
|
|
48
|
+
targets: NonEmptyArray<Fqn>;
|
|
49
|
+
}
|
|
50
50
|
}
|
|
51
|
-
export type ChangeView = ChangeView.
|
|
51
|
+
export type ChangeView = ChangeView.ChangeAutoLayout | ChangeView.ChangeElementStyle;
|
|
52
52
|
export interface ChangeViewRequestParams {
|
|
53
53
|
viewId: ViewID;
|
|
54
54
|
changes: NonEmptyArray<ChangeView>;
|
|
@@ -6,52 +6,55 @@ import { isTruthy } from "remeda";
|
|
|
6
6
|
import { ast } from "../ast.js";
|
|
7
7
|
import { logError } from "../logger.js";
|
|
8
8
|
export class LikeC4ScopeComputation extends DefaultScopeComputation {
|
|
9
|
-
computeExports(document, _cancelToken) {
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
async computeExports(document, _cancelToken) {
|
|
10
|
+
const docExports = [];
|
|
11
|
+
try {
|
|
12
12
|
const { specifications, models, views } = document.parseResult.value;
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
for (const spec of specifications.flatMap((s) => [
|
|
14
|
+
...s.elements,
|
|
15
|
+
...s.relationships,
|
|
16
|
+
...s.tags
|
|
17
|
+
])) {
|
|
18
|
+
try {
|
|
19
|
+
if (ast.isSpecificationTag(spec)) {
|
|
20
|
+
if (spec.tag && isTruthy(spec.tag.name)) {
|
|
21
|
+
docExports.push(
|
|
22
|
+
this.descriptions.createDescription(spec.tag, "#" + spec.tag.name, document)
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
15
27
|
if (spec.kind && isTruthy(spec.kind.name)) {
|
|
16
28
|
docExports.push(
|
|
17
29
|
this.descriptions.createDescription(spec.kind, spec.kind.name, document)
|
|
18
30
|
);
|
|
19
31
|
}
|
|
32
|
+
} catch (e) {
|
|
33
|
+
logError(e);
|
|
20
34
|
}
|
|
21
|
-
} catch (e) {
|
|
22
|
-
logError(e);
|
|
23
|
-
}
|
|
24
|
-
try {
|
|
25
|
-
for (const spec of specifications.flatMap((s) => s.tags)) {
|
|
26
|
-
if (spec.tag && isTruthy(spec.tag.name)) {
|
|
27
|
-
docExports.push(
|
|
28
|
-
this.descriptions.createDescription(spec.tag, "#" + spec.tag.name, document)
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
} catch (e) {
|
|
33
|
-
logError(e);
|
|
34
35
|
}
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
for (const elAst of models.flatMap((m) => m.elements)) {
|
|
37
|
+
try {
|
|
37
38
|
if (ast.isElement(elAst) && isTruthy(elAst.name)) {
|
|
38
39
|
docExports.push(this.descriptions.createDescription(elAst, elAst.name, document));
|
|
39
40
|
}
|
|
41
|
+
} catch (e) {
|
|
42
|
+
logError(e);
|
|
40
43
|
}
|
|
41
|
-
} catch (e) {
|
|
42
|
-
logError(e);
|
|
43
44
|
}
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
for (const viewAst of views.flatMap((v) => v.views)) {
|
|
46
|
+
try {
|
|
46
47
|
if (isTruthy(viewAst.name)) {
|
|
47
48
|
docExports.push(this.descriptions.createDescription(viewAst, viewAst.name, document));
|
|
48
49
|
}
|
|
50
|
+
} catch (e) {
|
|
51
|
+
logError(e);
|
|
49
52
|
}
|
|
50
|
-
} catch (e) {
|
|
51
|
-
logError(e);
|
|
52
53
|
}
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
} catch (e) {
|
|
55
|
+
logError(e);
|
|
56
|
+
}
|
|
57
|
+
return docExports;
|
|
55
58
|
}
|
|
56
59
|
computeLocalScopes(document, _cancelToken) {
|
|
57
60
|
return new Promise((resolve) => {
|
|
@@ -95,9 +98,11 @@ export class LikeC4ScopeComputation extends DefaultScopeComputation {
|
|
|
95
98
|
}
|
|
96
99
|
}
|
|
97
100
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
localScope.
|
|
101
|
+
if (nestedScopes.size > 0) {
|
|
102
|
+
for (const [name, descriptions] of nestedScopes.entriesGroupedByKey()) {
|
|
103
|
+
if (!localScope.has(name) && descriptions.length === 1) {
|
|
104
|
+
localScope.add(name, descriptions[0]);
|
|
105
|
+
}
|
|
101
106
|
}
|
|
102
107
|
}
|
|
103
108
|
scopes.addAll(container, localScope.values());
|
|
@@ -17,7 +17,7 @@ export class NodeKindProvider {
|
|
|
17
17
|
case (ast.isModel(node) || ast.isModelViews(node) || ast.isSpecificationRule(node) || hasType(ast.Model) || hasType(ast.ModelViews) || hasType(ast.SpecificationRule)): {
|
|
18
18
|
return SymbolKind.Namespace;
|
|
19
19
|
}
|
|
20
|
-
case (ast.
|
|
20
|
+
case (ast.isLikeC4View(node) || hasType(ast.LikeC4View)): {
|
|
21
21
|
return SymbolKind.Class;
|
|
22
22
|
}
|
|
23
23
|
case (ast.isTag(node) || hasType(ast.Tag) || (ast.isSpecificationTag(node) || hasType(ast.SpecificationTag))): {
|
|
@@ -43,6 +43,8 @@ export class NodeKindProvider {
|
|
|
43
43
|
return CompletionItemKind.Module;
|
|
44
44
|
case SymbolKind.Class:
|
|
45
45
|
return CompletionItemKind.Class;
|
|
46
|
+
case SymbolKind.Enum:
|
|
47
|
+
return CompletionItemKind.Enum;
|
|
46
48
|
case SymbolKind.EnumMember:
|
|
47
49
|
return CompletionItemKind.EnumMember;
|
|
48
50
|
case SymbolKind.TypeParameter:
|
|
@@ -52,7 +54,7 @@ export class NodeKindProvider {
|
|
|
52
54
|
case SymbolKind.Event:
|
|
53
55
|
return CompletionItemKind.Event;
|
|
54
56
|
default:
|
|
55
|
-
return CompletionItemKind.
|
|
57
|
+
return CompletionItemKind.Reference;
|
|
56
58
|
}
|
|
57
59
|
}
|
|
58
60
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ValidationCheck } from 'langium';
|
|
2
|
+
import { ast } from '../ast';
|
|
3
|
+
import type { LikeC4Services } from '../module';
|
|
4
|
+
export declare const dynamicViewRulePredicate: (_services: LikeC4Services) => ValidationCheck<ast.DynamicViewRulePredicate>;
|
|
5
|
+
//# sourceMappingURL=dynamic-view-rule.d.ts.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { nonexhaustive } from "@likec4/core";
|
|
2
|
+
import { ast } from "../ast.js";
|
|
3
|
+
import { logError } from "../logger.js";
|
|
4
|
+
export const dynamicViewRulePredicate = (_services) => {
|
|
5
|
+
return (el, accept) => {
|
|
6
|
+
try {
|
|
7
|
+
for (const expr of el.expressions) {
|
|
8
|
+
switch (true) {
|
|
9
|
+
case ast.isElementRef(expr):
|
|
10
|
+
case ast.isDescedantsExpr(expr):
|
|
11
|
+
case ast.isCustomElementExpr(expr):
|
|
12
|
+
case ast.isExpandElementExpr(expr):
|
|
13
|
+
return;
|
|
14
|
+
case ast.isRelationExpr(expr):
|
|
15
|
+
case ast.isInOutExpr(expr):
|
|
16
|
+
case ast.isIncomingExpr(expr):
|
|
17
|
+
case ast.isOutgoingExpr(expr):
|
|
18
|
+
case ast.isElementKindExpr(expr):
|
|
19
|
+
case ast.isElementTagExpr(expr):
|
|
20
|
+
case ast.isWildcardExpr(expr):
|
|
21
|
+
return accept("warning", `Expression is not supported by dynamic views`, {
|
|
22
|
+
node: expr
|
|
23
|
+
});
|
|
24
|
+
default:
|
|
25
|
+
nonexhaustive(expr);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
} catch (e) {
|
|
29
|
+
logError(e);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ValidationCheck } from 'langium';
|
|
2
|
+
import { ast } from '../ast';
|
|
3
|
+
import type { LikeC4Services } from '../module';
|
|
4
|
+
export declare const dynamicViewStep: (services: LikeC4Services) => ValidationCheck<ast.DynamicViewStep>;
|
|
5
|
+
//# sourceMappingURL=dynamic-view-step.d.ts.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { isAncestor } from "@likec4/core";
|
|
2
|
+
import { elementRef } from "../elementRef.js";
|
|
3
|
+
import { logError } from "../logger.js";
|
|
4
|
+
export const dynamicViewStep = (services) => {
|
|
5
|
+
const fqnIndex = services.likec4.FqnIndex;
|
|
6
|
+
return (el, accept) => {
|
|
7
|
+
try {
|
|
8
|
+
const sourceEl = elementRef(el.source);
|
|
9
|
+
const source = sourceEl && fqnIndex.getFqn(sourceEl);
|
|
10
|
+
if (!source) {
|
|
11
|
+
accept("error", "Source not found (not parsed/indexed yet)", {
|
|
12
|
+
node: el,
|
|
13
|
+
property: "source"
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
const targetEl = elementRef(el.target);
|
|
17
|
+
const target = targetEl && fqnIndex.getFqn(targetEl);
|
|
18
|
+
if (!target) {
|
|
19
|
+
accept("error", "Target not found (not parsed/indexed yet)", {
|
|
20
|
+
node: el,
|
|
21
|
+
property: "target"
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
if (source && target && (isAncestor(source, target) || isAncestor(target, source))) {
|
|
25
|
+
accept("error", "Invalid parent-child relationship", {
|
|
26
|
+
node: el
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
} catch (e) {
|
|
30
|
+
logError(e);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
};
|
package/dist/validation/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { logger } from "../logger.js";
|
|
2
|
+
import { dynamicViewRulePredicate } from "./dynamic-view-rule.js";
|
|
3
|
+
import { dynamicViewStep } from "./dynamic-view-step.js";
|
|
2
4
|
import { elementChecks } from "./element.js";
|
|
3
5
|
import { opacityPropertyRuleChecks } from "./property-checks.js";
|
|
4
6
|
import { relationChecks } from "./relation.js";
|
|
@@ -25,11 +27,13 @@ export function registerValidationChecks(services) {
|
|
|
25
27
|
SpecificationRule: specificationRuleChecks(services),
|
|
26
28
|
Model: modelRuleChecks(services),
|
|
27
29
|
ModelViews: modelViewsChecks(services),
|
|
28
|
-
|
|
30
|
+
DynamicViewStep: dynamicViewStep(services),
|
|
31
|
+
LikeC4View: viewChecks(services),
|
|
29
32
|
Element: elementChecks(services),
|
|
30
33
|
ElementKind: elementKindChecks(services),
|
|
31
34
|
Relation: relationChecks(services),
|
|
32
35
|
Tag: tagChecks(services),
|
|
36
|
+
DynamicViewRulePredicate: dynamicViewRulePredicate(services),
|
|
33
37
|
CustomElementExpr: customElementExprChecks(services),
|
|
34
38
|
ExpandElementExpr: expandElementExprChecks(services),
|
|
35
39
|
RelationshipKind: relationshipChecks(services),
|
|
@@ -4,6 +4,7 @@ export const expandElementExprChecks = (_services) => {
|
|
|
4
4
|
return (el, accept) => {
|
|
5
5
|
switch (true) {
|
|
6
6
|
case ast.isIncludePredicate(el.$container):
|
|
7
|
+
case ast.isDynamicViewRulePredicate(el.$container):
|
|
7
8
|
case ast.isViewRuleStyle(el.$container):
|
|
8
9
|
return;
|
|
9
10
|
case ast.isCustomElementExpr(el.$container):
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isNullish } from "remeda";
|
|
2
2
|
import { ast } from "../../ast.js";
|
|
3
3
|
export const outgoingExpressionChecks = (_services) => {
|
|
4
4
|
return (el, accept) => {
|
|
5
5
|
if (ast.isWildcardExpr(el.from)) {
|
|
6
6
|
const view = el.$container.$container.$container;
|
|
7
|
-
if (
|
|
7
|
+
if (view.$type === "ElementView" && isNullish(view.viewOf)) {
|
|
8
8
|
accept("warning", "Predicate is ignored as it concerns all relationships", {
|
|
9
9
|
node: el
|
|
10
10
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ValidationCheck } from 'langium';
|
|
2
2
|
import { ast } from '../ast';
|
|
3
3
|
import type { LikeC4Services } from '../module';
|
|
4
|
-
export declare const viewChecks: (services: LikeC4Services) => ValidationCheck<ast.
|
|
4
|
+
export declare const viewChecks: (services: LikeC4Services) => ValidationCheck<ast.LikeC4View>;
|
|
5
5
|
//# sourceMappingURL=view.d.ts.map
|
package/dist/validation/view.js
CHANGED
|
@@ -2,12 +2,10 @@ import { ast } from "../ast.js";
|
|
|
2
2
|
export const viewChecks = (services) => {
|
|
3
3
|
const index = services.shared.workspace.IndexManager;
|
|
4
4
|
return (el, accept) => {
|
|
5
|
-
if (el.extends) {
|
|
6
|
-
}
|
|
7
5
|
if (!el.name) {
|
|
8
6
|
return;
|
|
9
7
|
}
|
|
10
|
-
const anotherViews = index.allElements(ast.
|
|
8
|
+
const anotherViews = index.allElements(ast.LikeC4View).filter((n) => n.name === el.name).limit(2).count();
|
|
11
9
|
if (anotherViews > 1) {
|
|
12
10
|
accept("error", `Duplicate view '${el.name}'`, {
|
|
13
11
|
node: el,
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { isComputedElementView } from "@likec4/core";
|
|
1
2
|
import { find } from "remeda";
|
|
2
3
|
export function assignNavigateTo(views) {
|
|
3
4
|
const allElementViews = /* @__PURE__ */ new Map();
|
|
4
5
|
for (const v of views) {
|
|
5
|
-
if (v.viewOf && !v.extends) {
|
|
6
|
+
if (isComputedElementView(v) && v.viewOf && !v.extends) {
|
|
6
7
|
const viewsOf = allElementViews.get(v.viewOf) ?? [];
|
|
7
8
|
viewsOf.push(v.id);
|
|
8
9
|
allElementViews.set(v.viewOf, viewsOf);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type View } from '@likec4/core';
|
|
2
2
|
/**
|
|
3
3
|
* Resolve rules of extended views
|
|
4
4
|
* (Removes invalid views)
|
|
5
5
|
*/
|
|
6
|
-
export declare function resolveRulesExtendedViews<V extends Record<any,
|
|
6
|
+
export declare function resolveRulesExtendedViews<V extends Record<any, View>>(unresolvedViews: V): V;
|
|
7
7
|
//# sourceMappingURL=resolve-extended-views.d.ts.map
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare function resolveRelativePaths(views:
|
|
1
|
+
import type { View } from '@likec4/core';
|
|
2
|
+
export declare function resolveRelativePaths(views: View[]): View[];
|
|
3
3
|
//# sourceMappingURL=resolve-relative-paths.d.ts.map
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { invariant } from "@likec4/core";
|
|
2
|
-
import {
|
|
3
|
-
import { hasAtLeast } from "remeda";
|
|
2
|
+
import { hasAtLeast, unique, zip } from "remeda";
|
|
4
3
|
function commonAncestorPath(views, sep = "/") {
|
|
5
4
|
if (views.length <= 1)
|
|
6
5
|
return "";
|
|
7
|
-
const uniqURIs =
|
|
6
|
+
const uniqURIs = unique(views.flatMap(({ docUri }) => docUri ? [docUri] : []));
|
|
8
7
|
if (uniqURIs.length === 0)
|
|
9
8
|
return "";
|
|
10
9
|
if (uniqURIs.length === 1) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@likec4/language-server",
|
|
3
3
|
"description": "LikeC4 Language Server",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.2.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bugs": "https://github.com/likec4/likec4/issues",
|
|
7
7
|
"homepage": "https://likec4.dev",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
}
|
|
62
62
|
},
|
|
63
63
|
"scripts": {
|
|
64
|
-
"typecheck": "tsc
|
|
64
|
+
"typecheck": "tsc -b",
|
|
65
65
|
"watch:langium": "langium generate --watch",
|
|
66
66
|
"watch:ts": "tsc --watch",
|
|
67
67
|
"generate": "langium generate",
|
|
@@ -74,8 +74,8 @@
|
|
|
74
74
|
"test": "vitest run"
|
|
75
75
|
},
|
|
76
76
|
"dependencies": {
|
|
77
|
-
"@likec4/core": "1.
|
|
78
|
-
"@likec4/graph": "1.
|
|
77
|
+
"@likec4/core": "1.2.0",
|
|
78
|
+
"@likec4/graph": "1.2.0",
|
|
79
79
|
"@total-typescript/ts-reset": "^0.5.1",
|
|
80
80
|
"fast-equals": "^5.0.1",
|
|
81
81
|
"langium": "^3.0.0",
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"vscode-uri": "3.0.8"
|
|
93
93
|
},
|
|
94
94
|
"devDependencies": {
|
|
95
|
-
"@types/node": "^20.
|
|
95
|
+
"@types/node": "^20.13.0",
|
|
96
96
|
"@types/object-hash": "^3.0.6",
|
|
97
97
|
"@types/string-hash": "^1",
|
|
98
98
|
"execa": "^9.1.0",
|
|
@@ -102,5 +102,5 @@
|
|
|
102
102
|
"unbuild": "^2.0.0",
|
|
103
103
|
"vitest": "~1.5.2"
|
|
104
104
|
},
|
|
105
|
-
"packageManager": "yarn@4.
|
|
105
|
+
"packageManager": "yarn@4.3.0"
|
|
106
106
|
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { type Fqn, type NonEmptyArray } from '@likec4/core';
|
|
2
|
-
import { TextEdit } from 'vscode-languageserver-protocol';
|
|
3
|
-
import { ast, type ParsedAstElementView, type ParsedLikeC4LangiumDocument } from '../ast';
|
|
4
|
-
import type { LikeC4Services } from '../module';
|
|
5
|
-
type ChangeViewStyleArg = {
|
|
6
|
-
view: ParsedAstElementView;
|
|
7
|
-
doc: ParsedLikeC4LangiumDocument;
|
|
8
|
-
viewAst: ast.ElementView;
|
|
9
|
-
key: string;
|
|
10
|
-
value: string;
|
|
11
|
-
targets: NonEmptyArray<Fqn>;
|
|
12
|
-
};
|
|
13
|
-
export declare function changeViewStyle(services: LikeC4Services, { view, viewAst, targets, key, value }: ChangeViewStyleArg): TextEdit[];
|
|
14
|
-
export {};
|
|
15
|
-
//# sourceMappingURL=changeViewStyle.d.ts.map
|