@likec4/language-server 0.40.0 → 0.42.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 +48 -0
- package/contrib/likec4.tmLanguage.json +73 -0
- package/dist/Rpc.d.ts +1 -0
- package/dist/ast.d.ts +117 -0
- package/dist/ast.js +46 -2
- package/dist/elementRef.d.ts +2 -1
- package/dist/generated/ast.d.ts +45 -3
- package/dist/generated/ast.js +36 -2
- package/dist/generated/grammar.d.ts +2 -0
- package/dist/generated/grammar.js +1 -1
- package/dist/generated/module.d.ts +6 -1
- package/dist/index.d.ts +1 -0
- package/dist/logger.d.ts +1 -0
- package/dist/lsp/CodeLensProvider.d.ts +1 -0
- package/dist/lsp/DocumentLinkProvider.d.ts +1 -0
- package/dist/lsp/DocumentSymbolProvider.d.ts +1 -0
- package/dist/lsp/HoverProvider.d.ts +1 -0
- package/dist/lsp/SemanticTokenProvider.d.ts +1 -0
- package/dist/lsp/SemanticTokenProvider.js +21 -14
- package/dist/lsp/index.d.ts +1 -0
- package/dist/model/fqn-computation.d.ts +1 -0
- package/dist/model/fqn-index.d.ts +1 -0
- package/dist/model/fqn-index.js +25 -19
- package/dist/model/index.d.ts +1 -0
- package/dist/model/model-builder.d.ts +2 -1
- package/dist/model/model-builder.js +21 -15
- package/dist/model/model-locator.d.ts +1 -0
- package/dist/model/model-parser.d.ts +1 -0
- package/dist/model/model-parser.js +21 -32
- package/dist/module.d.ts +1 -0
- package/dist/protocol.d.ts +1 -0
- package/dist/references/index.d.ts +1 -0
- package/dist/references/scope-computation.d.ts +3 -2
- package/dist/references/scope-computation.js +5 -0
- package/dist/references/scope-provider.d.ts +2 -1
- package/dist/shared/WorkspaceManager.d.ts +2 -1
- package/dist/shared/index.d.ts +1 -0
- package/dist/test/index.d.ts +1 -0
- package/dist/test/testServices.d.ts +7 -6
- package/dist/test/testServices.js +12 -3
- package/dist/utils.d.ts +1 -0
- package/dist/validation/element.d.ts +3 -0
- package/dist/validation/element.js +17 -13
- package/dist/validation/index.d.ts +1 -0
- package/dist/validation/index.js +3 -2
- package/dist/validation/relation.d.ts +2 -0
- package/dist/validation/relation.js +8 -4
- package/dist/validation/specification.d.ts +3 -0
- package/dist/validation/specification.js +12 -0
- package/dist/validation/view.d.ts +2 -0
- package/dist/view-utils/assignNavigateTo.d.ts +3 -0
- package/dist/view-utils/assignNavigateTo.js +20 -0
- package/dist/view-utils/index.d.ts +4 -0
- package/dist/view-utils/index.js +3 -0
- package/dist/view-utils/resolve-extended-views.d.ts +7 -0
- package/dist/view-utils/resolve-extended-views.js +41 -0
- package/dist/view-utils/resolve-relative-paths.d.ts +3 -0
- package/dist/view-utils/resolve-relative-paths.js +76 -0
- package/package.json +22 -14
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
-
ModelIndex,
|
|
3
|
-
assignNavigateTo,
|
|
4
2
|
compareByFqnHierarchically,
|
|
5
|
-
computeView,
|
|
6
3
|
invariant,
|
|
7
4
|
isStrictElementView,
|
|
8
|
-
parentFqn
|
|
9
|
-
resolveRulesExtendedViews,
|
|
10
|
-
resolveRelativePaths
|
|
5
|
+
parentFqn
|
|
11
6
|
} from "@likec4/core";
|
|
12
7
|
import {
|
|
13
8
|
DocumentState,
|
|
@@ -19,17 +14,19 @@ import { isValidLikeC4LangiumDocument } from "../ast.js";
|
|
|
19
14
|
import { logError, logWarnError, logger } from "../logger.js";
|
|
20
15
|
import { LikeC4WorkspaceManager } from "../shared/index.js";
|
|
21
16
|
import { printDocs, queueMicrotask } from "../utils.js";
|
|
17
|
+
import { assignNavigateTo, resolveRelativePaths, resolveRulesExtendedViews } from "../view-utils/index.js";
|
|
18
|
+
import { LikeC4ModelGraph, computeView } from "@likec4/graph";
|
|
22
19
|
function isRelativeLink(link) {
|
|
23
20
|
return link.startsWith(".") || link.startsWith("/");
|
|
24
21
|
}
|
|
25
22
|
function buildModel(docs) {
|
|
26
23
|
const c4Specification = {
|
|
27
|
-
kinds: {}
|
|
24
|
+
kinds: {},
|
|
25
|
+
relationships: {}
|
|
28
26
|
};
|
|
29
|
-
R.forEach(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
);
|
|
27
|
+
R.forEach(R.map(docs, R.prop("c4Specification")), (spec) => {
|
|
28
|
+
Object.assign(c4Specification.kinds, spec.kinds), Object.assign(c4Specification.relationships, spec.relationships);
|
|
29
|
+
});
|
|
33
30
|
const resolveLinks = (doc, links) => {
|
|
34
31
|
const base = new URL(doc.uri.toString());
|
|
35
32
|
return links.map(
|
|
@@ -84,6 +81,15 @@ function buildModel(docs) {
|
|
|
84
81
|
...model
|
|
85
82
|
}) => {
|
|
86
83
|
if (source in elements && target in elements) {
|
|
84
|
+
if (model.kind) {
|
|
85
|
+
const kind = c4Specification.relationships[model.kind];
|
|
86
|
+
return {
|
|
87
|
+
source,
|
|
88
|
+
target,
|
|
89
|
+
...kind,
|
|
90
|
+
...model
|
|
91
|
+
};
|
|
92
|
+
}
|
|
87
93
|
return {
|
|
88
94
|
source,
|
|
89
95
|
target,
|
|
@@ -134,8 +140,7 @@ function buildModel(docs) {
|
|
|
134
140
|
links: null,
|
|
135
141
|
rules: [
|
|
136
142
|
{
|
|
137
|
-
|
|
138
|
-
exprs: [
|
|
143
|
+
include: [
|
|
139
144
|
{
|
|
140
145
|
wildcard: true
|
|
141
146
|
}
|
|
@@ -199,7 +204,7 @@ ${printDocs(docs)}`);
|
|
|
199
204
|
if (!model) {
|
|
200
205
|
return null;
|
|
201
206
|
}
|
|
202
|
-
const index =
|
|
207
|
+
const index = new LikeC4ModelGraph(model);
|
|
203
208
|
const views = R.pipe(
|
|
204
209
|
R.values(model.views),
|
|
205
210
|
R.map((view) => computeView(view, index).view),
|
|
@@ -220,7 +225,8 @@ ${printDocs(docs)}`);
|
|
|
220
225
|
logger.warn(`[ModelBuilder] Cannot find view ${viewId}`);
|
|
221
226
|
return null;
|
|
222
227
|
}
|
|
223
|
-
const
|
|
228
|
+
const index = new LikeC4ModelGraph(model);
|
|
229
|
+
const result = computeView(view, index);
|
|
224
230
|
if (!result.isSuccess) {
|
|
225
231
|
logError(result.error);
|
|
226
232
|
return null;
|
|
@@ -11,7 +11,8 @@ import {
|
|
|
11
11
|
streamModel,
|
|
12
12
|
toAutoLayout,
|
|
13
13
|
toElementStyle,
|
|
14
|
-
toElementStyleExcludeDefaults
|
|
14
|
+
toElementStyleExcludeDefaults,
|
|
15
|
+
toRelationshipStyleExcludeDefaults
|
|
15
16
|
} from "../ast.js";
|
|
16
17
|
import { elementRef, fqnElementRef } from "../elementRef.js";
|
|
17
18
|
import { logError, logWarnError, logger } from "../logger.js";
|
|
@@ -22,33 +23,6 @@ export class LikeC4ModelParser {
|
|
|
22
23
|
this.fqnIndex = services.likec4.FqnIndex;
|
|
23
24
|
}
|
|
24
25
|
fqnIndex;
|
|
25
|
-
// public onParsed(callback: ModelParsedListener): Disposable {
|
|
26
|
-
// this.listeners.push(callback)
|
|
27
|
-
// return Disposable.create(() => {
|
|
28
|
-
// const index = this.listeners.indexOf(callback)
|
|
29
|
-
// if (index >= 0) {
|
|
30
|
-
// this.listeners.splice(index, 1)
|
|
31
|
-
// }
|
|
32
|
-
// })
|
|
33
|
-
// }
|
|
34
|
-
// protected async onValidated(docs: LangiumDocument[], cancelToken: CancellationToken): Promise<void> {
|
|
35
|
-
// let countOfChangedDocs = 0
|
|
36
|
-
// logger.debug(`[ModelParser] onValidated (${docs.length} docs)\n${printDocs(docs)}`)
|
|
37
|
-
// for (const doc of docs) {
|
|
38
|
-
// if (!isLikeC4LangiumDocument(doc)) {
|
|
39
|
-
// continue
|
|
40
|
-
// }
|
|
41
|
-
// countOfChangedDocs++
|
|
42
|
-
// try {
|
|
43
|
-
// await this.parseDocument(doc, cancelToken)
|
|
44
|
-
// } catch (cause) {
|
|
45
|
-
// logError(new InvalidModelError(`Error parsing document ${doc.uri.toString()}`, { cause }))
|
|
46
|
-
// }
|
|
47
|
-
// }
|
|
48
|
-
// if (countOfChangedDocs > 0) {
|
|
49
|
-
// this.notifyListeners()
|
|
50
|
-
// }
|
|
51
|
-
// }
|
|
52
26
|
parse(doc) {
|
|
53
27
|
const docs = Array.isArray(doc) ? doc : [doc];
|
|
54
28
|
logger.debug(`[ModelParser] onValidated (${docs.length} docs)
|
|
@@ -82,6 +56,22 @@ ${printDocs(docs)}`);
|
|
|
82
56
|
}
|
|
83
57
|
}
|
|
84
58
|
}
|
|
59
|
+
const relations_specs = doc.parseResult.value.specification?.relationships;
|
|
60
|
+
if (relations_specs) {
|
|
61
|
+
for (const { kind, props } of relations_specs) {
|
|
62
|
+
if (kind.name in specification.relationships) {
|
|
63
|
+
logger.warn(`Duplicate specification for kind ${kind.name}`);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
specification.relationships[kind.name] = toRelationshipStyleExcludeDefaults(
|
|
68
|
+
props
|
|
69
|
+
);
|
|
70
|
+
} catch (e) {
|
|
71
|
+
logWarnError(e);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
85
75
|
for (const el of streamModel(doc)) {
|
|
86
76
|
if (ast.isElement(el)) {
|
|
87
77
|
try {
|
|
@@ -146,6 +136,7 @@ ${printDocs(docs)}`);
|
|
|
146
136
|
const coupling = resolveRelationPoints(astNode);
|
|
147
137
|
const target = this.resolveFqn(coupling.target);
|
|
148
138
|
const source = this.resolveFqn(coupling.source);
|
|
139
|
+
const kind = astNode.kind?.ref?.name;
|
|
149
140
|
const hashdata = {
|
|
150
141
|
astPath: this.getAstNodePath(astNode),
|
|
151
142
|
source,
|
|
@@ -156,6 +147,7 @@ ${printDocs(docs)}`);
|
|
|
156
147
|
return {
|
|
157
148
|
id,
|
|
158
149
|
...hashdata,
|
|
150
|
+
...kind && { kind },
|
|
159
151
|
title
|
|
160
152
|
};
|
|
161
153
|
}
|
|
@@ -225,10 +217,7 @@ ${printDocs(docs)}`);
|
|
|
225
217
|
parseViewRule(astRule) {
|
|
226
218
|
if (ast.isViewRuleExpression(astRule)) {
|
|
227
219
|
const exprs = astRule.expressions.map((n) => this.parseExpression(n));
|
|
228
|
-
return {
|
|
229
|
-
isInclude: astRule.isInclude,
|
|
230
|
-
exprs
|
|
231
|
-
};
|
|
220
|
+
return astRule.isInclude ? { include: exprs } : { exclude: exprs };
|
|
232
221
|
}
|
|
233
222
|
if (ast.isViewRuleStyle(astRule)) {
|
|
234
223
|
const styleProps = toElementStyle(astRule.props);
|
package/dist/module.d.ts
CHANGED
package/dist/protocol.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { DefaultScopeComputation, type AstNodeDescription, type PrecomputedScopes } from 'langium';
|
|
1
|
+
import { DefaultScopeComputation, MultiMap, type AstNodeDescription, type PrecomputedScopes } from 'langium';
|
|
2
2
|
import type { CancellationToken } from 'vscode-languageserver';
|
|
3
3
|
import { ast, type LikeC4LangiumDocument } from '../ast';
|
|
4
4
|
type ElementsContainer = ast.Model | ast.ElementBody | ast.ExtendElementBody;
|
|
5
5
|
export declare class LikeC4ScopeComputation extends DefaultScopeComputation {
|
|
6
6
|
computeExports(document: LikeC4LangiumDocument, _cancelToken: CancellationToken): Promise<AstNodeDescription[]>;
|
|
7
7
|
computeLocalScopes(document: LikeC4LangiumDocument, _cancelToken: CancellationToken): Promise<PrecomputedScopes>;
|
|
8
|
-
protected processContainer(container: ElementsContainer, scopes: PrecomputedScopes, document: LikeC4LangiumDocument):
|
|
8
|
+
protected processContainer(container: ElementsContainer, scopes: PrecomputedScopes, document: LikeC4LangiumDocument): MultiMap<string, AstNodeDescription>;
|
|
9
9
|
}
|
|
10
10
|
export {};
|
|
11
|
+
//# sourceMappingURL=scope-computation.d.ts.map
|
|
@@ -20,6 +20,11 @@ export class LikeC4ScopeComputation extends DefaultScopeComputation {
|
|
|
20
20
|
docExports.push(this.descriptions.createDescription(spec.tag, "#" + spec.tag.name, document));
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
|
+
for (const spec of specification.relationships) {
|
|
24
|
+
if (spec.kind && !isEmpty(spec.kind.name)) {
|
|
25
|
+
docExports.push(this.descriptions.createDescription(spec.kind, spec.kind.name, document));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
23
28
|
}
|
|
24
29
|
if (model && model.elements.length > 0) {
|
|
25
30
|
for (const elAst of model.elements) {
|
|
@@ -10,9 +10,10 @@ export declare class LikeC4ScopeProvider extends DefaultScopeProvider {
|
|
|
10
10
|
private scopeExtendElement;
|
|
11
11
|
private scopeElementView;
|
|
12
12
|
getScope(context: ReferenceInfo): Scope;
|
|
13
|
-
protected computeScope(node: AstNode, referenceType: string):
|
|
13
|
+
protected computeScope(node: AstNode, referenceType: string): Scope;
|
|
14
14
|
/**
|
|
15
15
|
* Create a global scope filtered for the given reference type.
|
|
16
16
|
*/
|
|
17
17
|
protected getGlobalScope(referenceType: string): Scope;
|
|
18
18
|
}
|
|
19
|
+
//# sourceMappingURL=scope-provider.d.ts.map
|
|
@@ -8,5 +8,6 @@ export declare class LikeC4WorkspaceManager extends DefaultWorkspaceManager {
|
|
|
8
8
|
* your language, which can be either loaded from provided files or constructed in memory.
|
|
9
9
|
*/
|
|
10
10
|
protected loadAdditionalDocuments(_folders: WorkspaceFolder[], _collector: (document: LangiumDocument) => void): Promise<void>;
|
|
11
|
-
workspace():
|
|
11
|
+
workspace(): WorkspaceFolder | null;
|
|
12
12
|
}
|
|
13
|
+
//# sourceMappingURL=WorkspaceManager.d.ts.map
|
package/dist/shared/index.d.ts
CHANGED
package/dist/test/index.d.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import type { LikeC4LangiumDocument } from '../ast';
|
|
2
2
|
export declare function createTestServices(workspace?: string): {
|
|
3
|
-
services:
|
|
3
|
+
services: import("../module").LikeC4Services;
|
|
4
4
|
parse: (input: string, uri?: string) => Promise<LikeC4LangiumDocument>;
|
|
5
5
|
validate: (input: string | LikeC4LangiumDocument, uri?: string) => Promise<{
|
|
6
6
|
document: LikeC4LangiumDocument;
|
|
7
|
-
diagnostics:
|
|
8
|
-
errors:
|
|
7
|
+
diagnostics: import("vscode-languageserver-types").Diagnostic[];
|
|
8
|
+
errors: string[];
|
|
9
9
|
}>;
|
|
10
10
|
validateAll: () => Promise<{
|
|
11
|
-
diagnostics:
|
|
12
|
-
errors:
|
|
11
|
+
diagnostics: import("vscode-languageserver-types").Diagnostic[];
|
|
12
|
+
errors: string[];
|
|
13
13
|
}>;
|
|
14
|
-
buildModel: () => Promise<
|
|
14
|
+
buildModel: () => Promise<import("@likec4/core").LikeC4Model>;
|
|
15
15
|
};
|
|
16
|
+
//# sourceMappingURL=testServices.d.ts.map
|
|
@@ -13,7 +13,9 @@ export function createTestServices(workspace = "file:///test/workspace") {
|
|
|
13
13
|
name: "test",
|
|
14
14
|
uri: workspaceUri.toString()
|
|
15
15
|
};
|
|
16
|
-
const initPromise = services.shared.workspace.WorkspaceManager.initializeWorkspace([
|
|
16
|
+
const initPromise = services.shared.workspace.WorkspaceManager.initializeWorkspace([
|
|
17
|
+
workspaceFolder
|
|
18
|
+
]);
|
|
17
19
|
void initPromise.finally(() => {
|
|
18
20
|
Object.assign(services.shared.workspace.WorkspaceManager, {
|
|
19
21
|
folders: [workspaceFolder]
|
|
@@ -22,8 +24,15 @@ export function createTestServices(workspace = "file:///test/workspace") {
|
|
|
22
24
|
let documentIndex = 1;
|
|
23
25
|
const parse = async (input, uri) => {
|
|
24
26
|
await initPromise;
|
|
25
|
-
const docUri = Utils.resolvePath(
|
|
26
|
-
|
|
27
|
+
const docUri = Utils.resolvePath(
|
|
28
|
+
workspaceUri,
|
|
29
|
+
"./src/",
|
|
30
|
+
uri ?? `${documentIndex++}${metaData.fileExtensions[0]}`
|
|
31
|
+
);
|
|
32
|
+
const document = services.shared.workspace.LangiumDocumentFactory.fromString(
|
|
33
|
+
stripIndent(input),
|
|
34
|
+
docUri
|
|
35
|
+
);
|
|
27
36
|
langiumDocuments.addDocument(document);
|
|
28
37
|
await documentBuilder.build([document], { validation: false });
|
|
29
38
|
return document;
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { type ValidationCheck } from 'langium';
|
|
1
3
|
import type { ast } from '../ast';
|
|
2
4
|
import type { LikeC4Services } from '../module';
|
|
3
5
|
export declare const elementChecks: (services: LikeC4Services) => ValidationCheck<ast.Element>;
|
|
6
|
+
//# sourceMappingURL=element.d.ts.map
|
|
@@ -12,19 +12,23 @@ export const elementChecks = (services) => {
|
|
|
12
12
|
}
|
|
13
13
|
const withSameFqn = fqnIndex.byFqn(fqn).filter((v) => v.el !== el).head();
|
|
14
14
|
if (withSameFqn) {
|
|
15
|
-
accept(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
15
|
+
accept(
|
|
16
|
+
"error",
|
|
17
|
+
`Duplicate element name ${el.name !== fqn ? el.name + " (" + fqn + ")" : el.name}`,
|
|
18
|
+
{
|
|
19
|
+
node: el,
|
|
20
|
+
property: "name",
|
|
21
|
+
relatedInformation: [
|
|
22
|
+
{
|
|
23
|
+
location: {
|
|
24
|
+
range: withSameFqn.el.$cstNode.range,
|
|
25
|
+
uri: getDocument(withSameFqn.el).uri.toString()
|
|
26
|
+
},
|
|
27
|
+
message: `Already defined here`
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
);
|
|
28
32
|
}
|
|
29
33
|
};
|
|
30
34
|
};
|
package/dist/validation/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import * as vscodeUri from "vscode-uri";
|
|
|
2
2
|
import { logger } from "../logger.js";
|
|
3
3
|
import { elementChecks } from "./element.js";
|
|
4
4
|
import { relationChecks } from "./relation.js";
|
|
5
|
-
import { elementKindChecks, tagChecks } from "./specification.js";
|
|
5
|
+
import { elementKindChecks, tagChecks, relationshipChecks } from "./specification.js";
|
|
6
6
|
import { viewChecks } from "./view.js";
|
|
7
7
|
export function registerValidationChecks(services) {
|
|
8
8
|
logger.info("registerValidationChecks");
|
|
@@ -12,7 +12,8 @@ export function registerValidationChecks(services) {
|
|
|
12
12
|
Element: elementChecks(services),
|
|
13
13
|
ElementKind: elementKindChecks(services),
|
|
14
14
|
Relation: relationChecks(services),
|
|
15
|
-
Tag: tagChecks(services)
|
|
15
|
+
Tag: tagChecks(services),
|
|
16
|
+
RelationshipKind: relationshipChecks(services)
|
|
16
17
|
});
|
|
17
18
|
const connection = services.shared.lsp.Connection;
|
|
18
19
|
if (connection) {
|
|
@@ -19,10 +19,14 @@ export const relationChecks = (services) => {
|
|
|
19
19
|
sourceEl = elementRef(el.source);
|
|
20
20
|
} else {
|
|
21
21
|
if (!ast.isElementBody(el.$container)) {
|
|
22
|
-
accept(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
accept(
|
|
23
|
+
"error",
|
|
24
|
+
"Invalid relation, expected to have source defined or be inside the element",
|
|
25
|
+
{
|
|
26
|
+
node: el,
|
|
27
|
+
keyword: "->"
|
|
28
|
+
}
|
|
29
|
+
);
|
|
26
30
|
} else {
|
|
27
31
|
sourceEl = el.$container.$container;
|
|
28
32
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import type { ValidationCheck } from 'langium';
|
|
1
2
|
import { ast } from '../ast';
|
|
2
3
|
import type { LikeC4Services } from '../module';
|
|
3
4
|
export declare const elementKindChecks: (services: LikeC4Services) => ValidationCheck<ast.ElementKind>;
|
|
4
5
|
export declare const tagChecks: (services: LikeC4Services) => ValidationCheck<ast.Tag>;
|
|
6
|
+
export declare const relationshipChecks: (services: LikeC4Services) => ValidationCheck<ast.RelationshipKind>;
|
|
7
|
+
//# sourceMappingURL=specification.d.ts.map
|
|
@@ -23,3 +23,15 @@ export const tagChecks = (services) => {
|
|
|
23
23
|
}
|
|
24
24
|
};
|
|
25
25
|
};
|
|
26
|
+
export const relationshipChecks = (services) => {
|
|
27
|
+
const index = services.shared.workspace.IndexManager;
|
|
28
|
+
return (node, accept) => {
|
|
29
|
+
const sameKinds = index.allElements(ast.RelationshipKind).filter((n) => n.name === node.name).limit(2).count();
|
|
30
|
+
if (sameKinds > 1) {
|
|
31
|
+
accept("error", `Duplicate RelationshipKind '${node.name}'`, {
|
|
32
|
+
node,
|
|
33
|
+
property: "name"
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { find } from "remeda";
|
|
2
|
+
export function assignNavigateTo(views) {
|
|
3
|
+
const allElementViews = /* @__PURE__ */ new Map();
|
|
4
|
+
for (const v of views) {
|
|
5
|
+
if (v.viewOf && !v.extends) {
|
|
6
|
+
const viewsOf = allElementViews.get(v.viewOf) ?? [];
|
|
7
|
+
viewsOf.push(v.id);
|
|
8
|
+
allElementViews.set(v.viewOf, viewsOf);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
for (const { id, nodes } of views) {
|
|
12
|
+
for (const node of nodes) {
|
|
13
|
+
const navigateTo = find(allElementViews.get(node.id) ?? [], (v) => v !== id);
|
|
14
|
+
if (navigateTo) {
|
|
15
|
+
node.navigateTo = navigateTo;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return views;
|
|
20
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type ElementView } from '@likec4/core';
|
|
2
|
+
/**
|
|
3
|
+
* Resolve rules of extended views
|
|
4
|
+
* (Removes invalid views)
|
|
5
|
+
*/
|
|
6
|
+
export declare function resolveRulesExtendedViews<V extends Record<any, ElementView>>(unresolvedViews: V): V;
|
|
7
|
+
//# sourceMappingURL=resolve-extended-views.d.ts.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import graphlib from "@dagrejs/graphlib";
|
|
2
|
+
import { isExtendsElementView } from "@likec4/core";
|
|
3
|
+
const { Graph, alg } = graphlib;
|
|
4
|
+
export function resolveRulesExtendedViews(unresolvedViews) {
|
|
5
|
+
const g = new Graph({
|
|
6
|
+
directed: true,
|
|
7
|
+
multigraph: false,
|
|
8
|
+
compound: false
|
|
9
|
+
});
|
|
10
|
+
for (const view of Object.values(unresolvedViews)) {
|
|
11
|
+
g.setNode(view.id);
|
|
12
|
+
if (isExtendsElementView(view)) {
|
|
13
|
+
g.setEdge(view.id, view.extends);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const cycles = alg.findCycles(g);
|
|
17
|
+
if (cycles.length > 0) {
|
|
18
|
+
cycles.flat().forEach((id) => g.removeNode(id));
|
|
19
|
+
}
|
|
20
|
+
const ordered = alg.postorder(g, g.sources());
|
|
21
|
+
return ordered.reduce((acc, id) => {
|
|
22
|
+
const view = unresolvedViews[id];
|
|
23
|
+
if (!view) {
|
|
24
|
+
return acc;
|
|
25
|
+
}
|
|
26
|
+
if (isExtendsElementView(view)) {
|
|
27
|
+
const extendsFrom = acc[view.extends];
|
|
28
|
+
if (!extendsFrom) {
|
|
29
|
+
return acc;
|
|
30
|
+
}
|
|
31
|
+
return Object.assign(acc, {
|
|
32
|
+
[view.id]: {
|
|
33
|
+
...extendsFrom,
|
|
34
|
+
...view,
|
|
35
|
+
rules: [...extendsFrom.rules, ...view.rules]
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return Object.assign(acc, { [view.id]: view });
|
|
40
|
+
}, {});
|
|
41
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { uniq, zip } from "rambdax";
|
|
2
|
+
import { hasAtLeast } from "remeda";
|
|
3
|
+
import { invariant } from "@likec4/core";
|
|
4
|
+
function commonAncestorPath(views, sep = "/") {
|
|
5
|
+
if (views.length <= 1)
|
|
6
|
+
return "";
|
|
7
|
+
const uniqURIs = uniq(views.flatMap(({ docUri }) => docUri ? [docUri] : []));
|
|
8
|
+
if (uniqURIs.length === 0)
|
|
9
|
+
return "";
|
|
10
|
+
if (uniqURIs.length === 1) {
|
|
11
|
+
invariant(hasAtLeast(uniqURIs, 1));
|
|
12
|
+
return new URL(uniqURIs[0]).pathname;
|
|
13
|
+
}
|
|
14
|
+
invariant(hasAtLeast(uniqURIs, 2), "Expected at least 2 unique URIs");
|
|
15
|
+
const [baseUri, ...tail] = uniqURIs;
|
|
16
|
+
const parts = new URL(baseUri).pathname.split(sep);
|
|
17
|
+
let endOfPrefix = parts.length;
|
|
18
|
+
for (const uri of tail) {
|
|
19
|
+
if (uri === baseUri) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const compare = new URL(uri).pathname.split(sep);
|
|
23
|
+
for (let i = 0; i < endOfPrefix; i++) {
|
|
24
|
+
if (compare[i] !== parts[i]) {
|
|
25
|
+
endOfPrefix = i;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (endOfPrefix === 0)
|
|
29
|
+
return "";
|
|
30
|
+
}
|
|
31
|
+
const prefix = parts.slice(0, endOfPrefix).join(sep);
|
|
32
|
+
return prefix.endsWith(sep) ? prefix : prefix + sep;
|
|
33
|
+
}
|
|
34
|
+
export function resolveRelativePaths(views) {
|
|
35
|
+
const commonPrefix = commonAncestorPath(views);
|
|
36
|
+
return views.map((view) => {
|
|
37
|
+
if (!view.docUri) {
|
|
38
|
+
return {
|
|
39
|
+
...view,
|
|
40
|
+
parts: []
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const path = new URL(view.docUri).pathname;
|
|
44
|
+
const parts = path.replace(commonPrefix, "").split("/");
|
|
45
|
+
parts.pop();
|
|
46
|
+
return {
|
|
47
|
+
...view,
|
|
48
|
+
parts
|
|
49
|
+
};
|
|
50
|
+
}).sort((a, b) => {
|
|
51
|
+
if (a.parts.length === b.parts.length) {
|
|
52
|
+
if (a.parts.length === 0) {
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
55
|
+
if (a.parts.length === 1 && hasAtLeast(a.parts, 1) && hasAtLeast(b.parts, 1)) {
|
|
56
|
+
return a.parts[0].localeCompare(b.parts[0]);
|
|
57
|
+
}
|
|
58
|
+
for (const [_a, _b] of zip(a.parts, b.parts)) {
|
|
59
|
+
const compare = _a.localeCompare(_b);
|
|
60
|
+
if (compare !== 0) {
|
|
61
|
+
return compare;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
66
|
+
return a.parts.length - b.parts.length;
|
|
67
|
+
}).map(({ parts, ...view }) => {
|
|
68
|
+
if (view.docUri) {
|
|
69
|
+
return {
|
|
70
|
+
...view,
|
|
71
|
+
relativePath: parts.join("/")
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return view;
|
|
75
|
+
});
|
|
76
|
+
}
|