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