@likec4/language-server 0.45.0 → 0.46.1
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 +9 -8
- package/dist/Rpc.js +14 -60
- package/dist/ast.d.ts +9 -10
- package/dist/ast.js +14 -22
- package/dist/elementRef.d.ts +5 -11
- package/dist/elementRef.js +5 -32
- package/dist/generated/ast.d.ts +62 -60
- package/dist/generated/ast.js +59 -33
- package/dist/generated/grammar.d.ts +1 -1
- package/dist/generated/grammar.js +1 -1
- package/dist/generated/module.d.ts +1 -1
- package/dist/generated/module.js +1 -0
- package/dist/lsp/CodeLensProvider.js +2 -1
- package/dist/lsp/DocumentSymbolProvider.d.ts +1 -1
- package/dist/lsp/DocumentSymbolProvider.js +8 -5
- package/dist/lsp/SemanticTokenProvider.js +29 -63
- package/dist/model/fqn-computation.js +5 -5
- package/dist/model/fqn-index.d.ts +2 -1
- package/dist/model/fqn-index.js +26 -31
- package/dist/model/model-builder.js +20 -11
- package/dist/model/model-locator.js +4 -11
- package/dist/model/model-parser.d.ts +6 -3
- package/dist/model/model-parser.js +41 -36
- package/dist/module.js +4 -0
- package/dist/protocol.d.ts +0 -3
- package/dist/protocol.js +1 -4
- package/dist/references/scope-computation.js +19 -17
- package/dist/references/scope-provider.js +19 -19
- package/dist/shared/NodeKindProvider.d.ts +13 -0
- package/dist/shared/NodeKindProvider.js +29 -0
- package/dist/test/testServices.js +8 -8
- package/dist/utils.js +1 -1
- package/dist/validation/index.js +13 -3
- package/dist/validation/relation.js +12 -18
- package/dist/validation/specification.d.ts +3 -0
- package/dist/validation/specification.js +30 -0
- package/package.json +9 -9
|
@@ -2,51 +2,53 @@ import {
|
|
|
2
2
|
DefaultScopeComputation,
|
|
3
3
|
MultiMap
|
|
4
4
|
} from "langium";
|
|
5
|
-
import { isEmpty } from "remeda";
|
|
5
|
+
import { hasAtLeast, isEmpty } from "remeda";
|
|
6
6
|
import { ast } from "../ast.js";
|
|
7
|
+
import { logError } from "../logger.js";
|
|
7
8
|
export class LikeC4ScopeComputation extends DefaultScopeComputation {
|
|
8
9
|
computeExports(document, _cancelToken) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
for (const spec of
|
|
10
|
+
try {
|
|
11
|
+
const { specifications, models, views } = document.parseResult.value;
|
|
12
|
+
const docExports = [];
|
|
13
|
+
for (const spec of specifications.flatMap((s) => s.elements)) {
|
|
13
14
|
if (spec.kind && !isEmpty(spec.kind.name)) {
|
|
14
15
|
docExports.push(this.descriptions.createDescription(spec.kind, spec.kind.name, document));
|
|
15
16
|
}
|
|
16
17
|
}
|
|
17
|
-
for (const spec of
|
|
18
|
+
for (const spec of specifications.flatMap((s) => s.tags)) {
|
|
18
19
|
if (spec.tag && !isEmpty(spec.tag.name)) {
|
|
19
20
|
docExports.push(this.descriptions.createDescription(spec.tag, spec.tag.name, document));
|
|
20
|
-
docExports.push(
|
|
21
|
+
docExports.push(
|
|
22
|
+
this.descriptions.createDescription(spec.tag, "#" + spec.tag.name, document)
|
|
23
|
+
);
|
|
21
24
|
}
|
|
22
25
|
}
|
|
23
|
-
for (const spec of
|
|
26
|
+
for (const spec of specifications.flatMap((s) => s.relationships)) {
|
|
24
27
|
if (spec.kind && !isEmpty(spec.kind.name)) {
|
|
25
28
|
docExports.push(this.descriptions.createDescription(spec.kind, spec.kind.name, document));
|
|
26
29
|
}
|
|
27
30
|
}
|
|
28
|
-
|
|
29
|
-
if (model && model.elements.length > 0) {
|
|
30
|
-
for (const elAst of model.elements) {
|
|
31
|
+
for (const elAst of models.flatMap((m) => m.elements)) {
|
|
31
32
|
if (ast.isElement(elAst) && !isEmpty(elAst.name)) {
|
|
32
33
|
docExports.push(this.descriptions.createDescription(elAst, elAst.name, document));
|
|
33
34
|
}
|
|
34
35
|
}
|
|
35
|
-
|
|
36
|
-
if (views && views.views.length > 0) {
|
|
37
|
-
for (const viewAst of views.views) {
|
|
36
|
+
for (const viewAst of views.flatMap((v) => v.views)) {
|
|
38
37
|
if (viewAst.name && !isEmpty(viewAst.name)) {
|
|
39
38
|
docExports.push(this.descriptions.createDescription(viewAst, viewAst.name, document));
|
|
40
39
|
}
|
|
41
40
|
}
|
|
41
|
+
return Promise.resolve(docExports);
|
|
42
|
+
} catch (e) {
|
|
43
|
+
logError(e);
|
|
44
|
+
return Promise.reject(e);
|
|
42
45
|
}
|
|
43
|
-
return Promise.resolve(docExports);
|
|
44
46
|
}
|
|
45
47
|
async computeLocalScopes(document, _cancelToken) {
|
|
46
48
|
const root = document.parseResult.value;
|
|
47
49
|
const scopes = new MultiMap();
|
|
48
|
-
if (root.
|
|
49
|
-
const nested = this.processContainer(root.
|
|
50
|
+
if (hasAtLeast(root.models, 1)) {
|
|
51
|
+
const nested = this.processContainer(root.models[0], scopes, document);
|
|
50
52
|
scopes.addAll(root, nested.values());
|
|
51
53
|
}
|
|
52
54
|
return Promise.resolve(scopes);
|
|
@@ -4,13 +4,13 @@ import {
|
|
|
4
4
|
EMPTY_STREAM,
|
|
5
5
|
StreamImpl,
|
|
6
6
|
StreamScope,
|
|
7
|
+
findNodeForProperty,
|
|
7
8
|
getDocument,
|
|
8
9
|
stream,
|
|
9
|
-
findNodeForProperty,
|
|
10
10
|
toDocumentSegment
|
|
11
11
|
} from "langium";
|
|
12
12
|
import { ast } from "../ast.js";
|
|
13
|
-
import { elementRef,
|
|
13
|
+
import { elementRef, getFqnElementRef } from "../elementRef.js";
|
|
14
14
|
import { logError } from "../logger.js";
|
|
15
15
|
function toAstNodeDescription(entry) {
|
|
16
16
|
const $cstNode = findNodeForProperty(entry.el.$cstNode, "name");
|
|
@@ -56,41 +56,41 @@ export class LikeC4ScopeProvider extends DefaultScopeProvider {
|
|
|
56
56
|
);
|
|
57
57
|
}
|
|
58
58
|
scopeElementRef(ref) {
|
|
59
|
-
|
|
60
|
-
if (!ast.isElementRef(parentNode)) {
|
|
61
|
-
throw new Error("Expected be inside ElementRef");
|
|
62
|
-
}
|
|
63
|
-
return this.uniqueDescedants(() => parentNode.el.ref);
|
|
59
|
+
return this.uniqueDescedants(() => ref.el.ref);
|
|
64
60
|
}
|
|
65
61
|
scopeExtendElement(extend) {
|
|
66
62
|
return this.uniqueDescedants(() => elementRef(extend.element));
|
|
67
63
|
}
|
|
68
64
|
scopeElementView({ viewOf, extends: ext }) {
|
|
65
|
+
if (viewOf) {
|
|
66
|
+
return stream([viewOf]).flatMap((v) => {
|
|
67
|
+
const el = elementRef(v);
|
|
68
|
+
return el ? this.descriptions.createDescription(el, el.name) : [];
|
|
69
|
+
}).concat(this.uniqueDescedants(() => elementRef(viewOf)));
|
|
70
|
+
}
|
|
69
71
|
if (ext) {
|
|
70
72
|
return stream([ext]).flatMap((v) => {
|
|
71
73
|
const view = v.view.ref;
|
|
72
74
|
return view ? this.scopeElementView(view) : EMPTY_STREAM;
|
|
73
75
|
});
|
|
74
76
|
}
|
|
75
|
-
if (viewOf) {
|
|
76
|
-
return this.uniqueDescedants(() => elementRef(viewOf));
|
|
77
|
-
}
|
|
78
77
|
return EMPTY_STREAM;
|
|
79
78
|
}
|
|
80
79
|
getScope(context) {
|
|
81
80
|
const referenceType = this.reflection.getReferenceType(context);
|
|
82
81
|
try {
|
|
83
82
|
const container = context.container;
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
const parent = parentFqnElementRef(container);
|
|
90
|
-
return new StreamScope(this.directChildrenOf(parent));
|
|
83
|
+
if (ast.isFqnElementRef(container) && context.property === "el") {
|
|
84
|
+
const parent = container.parent;
|
|
85
|
+
if (!parent) {
|
|
86
|
+
return this.getGlobalScope(referenceType);
|
|
91
87
|
}
|
|
92
|
-
|
|
93
|
-
|
|
88
|
+
return new StreamScope(this.directChildrenOf(getFqnElementRef(parent)));
|
|
89
|
+
}
|
|
90
|
+
if (ast.isElementRef(container) && context.property === "el") {
|
|
91
|
+
const parent = container.parent;
|
|
92
|
+
if (parent) {
|
|
93
|
+
return new StreamScope(this.scopeElementRef(parent));
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
return this.computeScope(container, referenceType);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type AstNode, type AstNodeDescription } from 'langium';
|
|
2
|
+
import { CompletionItemKind, SymbolKind } from 'vscode-languageserver-protocol';
|
|
3
|
+
export declare class NodeKindProvider implements NodeKindProvider {
|
|
4
|
+
/**
|
|
5
|
+
* Returns a `SymbolKind` as used by `WorkspaceSymbolProvider` or `DocumentSymbolProvider`.
|
|
6
|
+
*/
|
|
7
|
+
getSymbolKind(_node: AstNode | AstNodeDescription): SymbolKind;
|
|
8
|
+
/**
|
|
9
|
+
* Returns a `CompletionItemKind` as used by the `CompletionProvider`.
|
|
10
|
+
*/
|
|
11
|
+
getCompletionItemKind(node: AstNode | AstNodeDescription): CompletionItemKind;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=NodeKindProvider.d.ts.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { isAstNode } from "langium";
|
|
2
|
+
import { CompletionItemKind, SymbolKind } from "vscode-languageserver-protocol";
|
|
3
|
+
import { ast } from "../ast.js";
|
|
4
|
+
export class NodeKindProvider {
|
|
5
|
+
/**
|
|
6
|
+
* Returns a `SymbolKind` as used by `WorkspaceSymbolProvider` or `DocumentSymbolProvider`.
|
|
7
|
+
*/
|
|
8
|
+
getSymbolKind(_node) {
|
|
9
|
+
return SymbolKind.Field;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Returns a `CompletionItemKind` as used by the `CompletionProvider`.
|
|
13
|
+
*/
|
|
14
|
+
getCompletionItemKind(node) {
|
|
15
|
+
const hasType = (type) => "type" in node && node.type === type;
|
|
16
|
+
switch (true) {
|
|
17
|
+
case (isAstNode(node) ? ast.isElement(node) : node.type === ast.Element): {
|
|
18
|
+
return CompletionItemKind.Variable;
|
|
19
|
+
}
|
|
20
|
+
case (ast.isElementKind(node) || ast.isRelationshipKind(node) || hasType(ast.ElementKind) || hasType(ast.RelationshipKind)): {
|
|
21
|
+
return CompletionItemKind.Class;
|
|
22
|
+
}
|
|
23
|
+
case (ast.isTag(node) || hasType(ast.Tag)): {
|
|
24
|
+
return CompletionItemKind.EnumMember;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return CompletionItemKind.Reference;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -13,17 +13,19 @@ export function createTestServices(workspace = "file:///test/workspace") {
|
|
|
13
13
|
name: "test",
|
|
14
14
|
uri: workspaceUri.toString()
|
|
15
15
|
};
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
let isInitialized = false;
|
|
17
|
+
const init = async () => {
|
|
18
|
+
if (isInitialized)
|
|
19
|
+
return Promise.resolve();
|
|
20
|
+
isInitialized = true;
|
|
21
|
+
await services.shared.workspace.WorkspaceManager.initializeWorkspace([workspaceFolder]);
|
|
20
22
|
Object.assign(services.shared.workspace.WorkspaceManager, {
|
|
21
23
|
folders: [workspaceFolder]
|
|
22
24
|
});
|
|
23
|
-
}
|
|
25
|
+
};
|
|
24
26
|
let documentIndex = 1;
|
|
25
27
|
const parse = async (input, uri) => {
|
|
26
|
-
await
|
|
28
|
+
await init();
|
|
27
29
|
const docUri = Utils.resolvePath(
|
|
28
30
|
workspaceUri,
|
|
29
31
|
"./src/",
|
|
@@ -38,7 +40,6 @@ export function createTestServices(workspace = "file:///test/workspace") {
|
|
|
38
40
|
return document;
|
|
39
41
|
};
|
|
40
42
|
const validate = async (input, uri) => {
|
|
41
|
-
await initPromise;
|
|
42
43
|
const document = typeof input === "string" ? await parse(input, uri) : input;
|
|
43
44
|
await documentBuilder.build([document], { validation: true });
|
|
44
45
|
const diagnostics = document.diagnostics ?? [];
|
|
@@ -50,7 +51,6 @@ export function createTestServices(workspace = "file:///test/workspace") {
|
|
|
50
51
|
};
|
|
51
52
|
};
|
|
52
53
|
const validateAll = async () => {
|
|
53
|
-
await initPromise;
|
|
54
54
|
const docs = langiumDocuments.all.toArray();
|
|
55
55
|
await documentBuilder.build(docs, { validation: true });
|
|
56
56
|
const diagnostics = docs.flatMap((doc) => doc.diagnostics ?? []);
|
package/dist/utils.js
CHANGED
package/dist/validation/index.js
CHANGED
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
import { logger } from "../logger.js";
|
|
2
2
|
import { elementChecks } from "./element.js";
|
|
3
3
|
import { relationChecks } from "./relation.js";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
elementKindChecks,
|
|
6
|
+
modelRuleChecks,
|
|
7
|
+
modelViewsChecks,
|
|
8
|
+
relationshipChecks,
|
|
9
|
+
specificationRuleChecks,
|
|
10
|
+
tagChecks
|
|
11
|
+
} from "./specification.js";
|
|
5
12
|
import { viewChecks } from "./view.js";
|
|
6
13
|
import { incomingExpressionChecks, outgoingExpressionChecks } from "./view-predicates/index.js";
|
|
7
14
|
export function registerValidationChecks(services) {
|
|
8
15
|
logger.info("registerValidationChecks");
|
|
9
16
|
const registry = services.validation.ValidationRegistry;
|
|
10
17
|
registry.register({
|
|
18
|
+
SpecificationRule: specificationRuleChecks(services),
|
|
19
|
+
Model: modelRuleChecks(services),
|
|
20
|
+
ModelViews: modelViewsChecks(services),
|
|
11
21
|
ElementView: viewChecks(services),
|
|
12
22
|
Element: elementChecks(services),
|
|
13
23
|
ElementKind: elementKindChecks(services),
|
|
@@ -16,10 +26,10 @@ export function registerValidationChecks(services) {
|
|
|
16
26
|
RelationshipKind: relationshipChecks(services),
|
|
17
27
|
IncomingExpression: incomingExpressionChecks(services),
|
|
18
28
|
OutgoingExpression: outgoingExpressionChecks(services)
|
|
19
|
-
});
|
|
29
|
+
}, "slow");
|
|
20
30
|
const connection = services.shared.lsp.Connection;
|
|
21
31
|
if (connection) {
|
|
22
|
-
services.shared.workspace.DocumentBuilder.onUpdate((
|
|
32
|
+
services.shared.workspace.DocumentBuilder.onUpdate((_, deleted) => {
|
|
23
33
|
for (const uri of deleted) {
|
|
24
34
|
void connection.sendDiagnostics({
|
|
25
35
|
uri: uri.toString(),
|
|
@@ -15,31 +15,25 @@ export const relationChecks = (services) => {
|
|
|
15
15
|
});
|
|
16
16
|
}
|
|
17
17
|
let sourceEl;
|
|
18
|
-
if (
|
|
18
|
+
if (ast.isExplicitRelation(el)) {
|
|
19
19
|
sourceEl = elementRef(el.source);
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
{
|
|
26
|
-
node: el,
|
|
27
|
-
keyword: "->"
|
|
28
|
-
}
|
|
29
|
-
);
|
|
30
|
-
} else {
|
|
31
|
-
sourceEl = el.$container.$container;
|
|
20
|
+
if (!sourceEl) {
|
|
21
|
+
return accept("error", "Source not found (not parsed/indexed yet)", {
|
|
22
|
+
node: el,
|
|
23
|
+
property: "source"
|
|
24
|
+
});
|
|
32
25
|
}
|
|
26
|
+
} else {
|
|
27
|
+
sourceEl = el.$container.$container;
|
|
33
28
|
}
|
|
34
|
-
const source =
|
|
35
|
-
if (
|
|
29
|
+
const source = fqnIndex.getFqn(sourceEl);
|
|
30
|
+
if (!source) {
|
|
36
31
|
accept("error", "Source not found (not parsed/indexed yet)", {
|
|
37
|
-
node: el
|
|
38
|
-
property: "source"
|
|
32
|
+
node: el
|
|
39
33
|
});
|
|
40
34
|
}
|
|
41
35
|
if (source && target && isSameHierarchy(source, target)) {
|
|
42
|
-
|
|
36
|
+
accept("error", "Invalid parent-child relationship", {
|
|
43
37
|
node: el
|
|
44
38
|
});
|
|
45
39
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { ValidationCheck } from 'langium';
|
|
2
2
|
import { ast } from '../ast';
|
|
3
3
|
import type { LikeC4Services } from '../module';
|
|
4
|
+
export declare const specificationRuleChecks: (_: LikeC4Services) => ValidationCheck<ast.SpecificationRule>;
|
|
5
|
+
export declare const modelRuleChecks: (_: LikeC4Services) => ValidationCheck<ast.Model>;
|
|
6
|
+
export declare const modelViewsChecks: (_: LikeC4Services) => ValidationCheck<ast.ModelViews>;
|
|
4
7
|
export declare const elementKindChecks: (services: LikeC4Services) => ValidationCheck<ast.ElementKind>;
|
|
5
8
|
export declare const tagChecks: (services: LikeC4Services) => ValidationCheck<ast.Tag>;
|
|
6
9
|
export declare const relationshipChecks: (services: LikeC4Services) => ValidationCheck<ast.RelationshipKind>;
|
|
@@ -1,4 +1,34 @@
|
|
|
1
1
|
import { ast } from "../ast.js";
|
|
2
|
+
export const specificationRuleChecks = (_) => {
|
|
3
|
+
return (node, accept) => {
|
|
4
|
+
if (node.$containerIndex && node.$containerIndex > 0) {
|
|
5
|
+
accept("error", `Only one specification per document is allowed`, {
|
|
6
|
+
node,
|
|
7
|
+
property: "name"
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
export const modelRuleChecks = (_) => {
|
|
13
|
+
return (node, accept) => {
|
|
14
|
+
if (node.$containerIndex && node.$containerIndex > 0) {
|
|
15
|
+
accept("error", `Only one model per document is allowed`, {
|
|
16
|
+
node,
|
|
17
|
+
property: "name"
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
export const modelViewsChecks = (_) => {
|
|
23
|
+
return (node, accept) => {
|
|
24
|
+
if (node.$containerIndex && node.$containerIndex > 0) {
|
|
25
|
+
accept("error", `Only one views block per document is allowed`, {
|
|
26
|
+
node,
|
|
27
|
+
property: "name"
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
};
|
|
2
32
|
export const elementKindChecks = (services) => {
|
|
3
33
|
const index = services.shared.workspace.IndexManager;
|
|
4
34
|
return (node, accept) => {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@likec4/language-server",
|
|
3
3
|
"description": "LikeC4 Language Server",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.46.1",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bugs": "https://github.com/likec4/likec4/issues",
|
|
7
7
|
"homepage": "https://likec4.dev",
|
|
@@ -50,24 +50,24 @@
|
|
|
50
50
|
"test": "vitest run"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@likec4/core": "0.
|
|
54
|
-
"@likec4/graph": "0.
|
|
55
|
-
"langium": "^2.
|
|
53
|
+
"@likec4/core": "0.46.1",
|
|
54
|
+
"@likec4/graph": "0.46.1",
|
|
55
|
+
"langium": "^2.1.2",
|
|
56
56
|
"object-hash": "^3.0.0",
|
|
57
57
|
"p-debounce": "^4.0.0",
|
|
58
58
|
"p-throttle": "^5.1.0",
|
|
59
59
|
"rambdax": "^9.1.1",
|
|
60
|
-
"remeda": "^1.
|
|
60
|
+
"remeda": "^1.29.0",
|
|
61
61
|
"strip-indent": "^4.0.0",
|
|
62
|
-
"vscode-languageserver": "
|
|
63
|
-
"vscode-languageserver-protocol": "
|
|
64
|
-
"vscode-uri": "
|
|
62
|
+
"vscode-languageserver": "9.0.1",
|
|
63
|
+
"vscode-languageserver-protocol": "3.17.5",
|
|
64
|
+
"vscode-uri": "3.0.8"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
67
|
"@types/node": "^20.8.7",
|
|
68
68
|
"@types/object-hash": "^3.0.5",
|
|
69
69
|
"execa": "^8.0.1",
|
|
70
|
-
"langium-cli": "^2.0
|
|
70
|
+
"langium-cli": "^2.1.0",
|
|
71
71
|
"npm-run-all": "^4.1.5",
|
|
72
72
|
"typescript": "^5.2.2",
|
|
73
73
|
"unbuild": "^2.0.0",
|