@likec4/language-server 0.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/dist/ast.d.ts +72 -0
- package/dist/ast.js +131 -0
- package/dist/elementRef.d.ts +6 -0
- package/dist/elementRef.js +39 -0
- package/dist/generated/ast.d.ts +346 -0
- package/dist/generated/ast.js +353 -0
- package/dist/generated/grammar.d.ts +6 -0
- package/dist/generated/grammar.js +2259 -0
- package/dist/generated/module.d.ts +9 -0
- package/dist/generated/module.js +26 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +16 -0
- package/dist/logger.d.ts +8 -0
- package/dist/logger.js +20 -0
- package/dist/lsp/CodeLensProvider.d.ts +8 -0
- package/dist/lsp/CodeLensProvider.js +35 -0
- package/dist/lsp/DocumentSymbolProvider.d.ts +21 -0
- package/dist/lsp/DocumentSymbolProvider.js +138 -0
- package/dist/lsp/HoverProvider.d.ts +8 -0
- package/dist/lsp/HoverProvider.js +57 -0
- package/dist/lsp/SemanticTokenProvider.d.ts +6 -0
- package/dist/lsp/SemanticTokenProvider.js +245 -0
- package/dist/lsp/index.d.ts +4 -0
- package/dist/lsp/index.js +4 -0
- package/dist/model/fqn-index.d.ts +17 -0
- package/dist/model/fqn-index.js +136 -0
- package/dist/model/index.d.ts +3 -0
- package/dist/model/index.js +3 -0
- package/dist/model/model-builder.d.ts +26 -0
- package/dist/model/model-builder.js +325 -0
- package/dist/model/model-locator.d.ts +16 -0
- package/dist/model/model-locator.js +108 -0
- package/dist/module.d.ts +18 -0
- package/dist/module.js +57 -0
- package/dist/protocol.d.ts +35 -0
- package/dist/protocol.js +19 -0
- package/dist/references/index.d.ts +2 -0
- package/dist/references/index.js +2 -0
- package/dist/references/scope-computation.d.ts +10 -0
- package/dist/references/scope-computation.js +76 -0
- package/dist/references/scope-provider.d.ts +15 -0
- package/dist/references/scope-provider.js +110 -0
- package/dist/registerProtocolHandlers.d.ts +2 -0
- package/dist/registerProtocolHandlers.js +49 -0
- package/dist/test/index.d.ts +1 -0
- package/dist/test/index.js +1 -0
- package/dist/test/testServices.d.ts +15 -0
- package/dist/test/testServices.js +53 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +7 -0
- package/dist/validation/element.d.ts +5 -0
- package/dist/validation/element.js +20 -0
- package/dist/validation/index.d.ts +2 -0
- package/dist/validation/index.js +20 -0
- package/dist/validation/specification.d.ts +5 -0
- package/dist/validation/specification.js +31 -0
- package/dist/validation/view.d.ts +4 -0
- package/dist/validation/view.js +19 -0
- package/package.json +84 -0
package/dist/module.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { createDefaultModule, inject, createDefaultSharedModule } from 'langium';
|
|
2
|
+
import { LikeC4GeneratedModule, LikeC4GeneratedSharedModule } from './generated/module';
|
|
3
|
+
import { LikeC4ScopeComputation, LikeC4ScopeProvider } from './references';
|
|
4
|
+
import { FqnIndex, LikeC4ModelBuilder, LikeC4ModelLocator } from './model';
|
|
5
|
+
import { registerValidationChecks } from './validation';
|
|
6
|
+
import { LikeC4CodeLensProvider, LikeC4DocumentSymbolProvider, LikeC4HoverProvider, LikeC4SemanticTokenProvider } from './lsp';
|
|
7
|
+
import { registerProtocolHandlers } from './registerProtocolHandlers';
|
|
8
|
+
function bind(Type) {
|
|
9
|
+
return (services) => new Type(services);
|
|
10
|
+
}
|
|
11
|
+
export const LikeC4Module = {
|
|
12
|
+
likec4: {
|
|
13
|
+
FqnIndex: bind(FqnIndex),
|
|
14
|
+
ModelBuilder: bind(LikeC4ModelBuilder),
|
|
15
|
+
ModelLocator: bind(LikeC4ModelLocator),
|
|
16
|
+
// Model: bind(LikeC4Model),
|
|
17
|
+
// Model: bind(LikeC4Model),
|
|
18
|
+
// SpecIndex: bind(LikeC4SpecIndex),
|
|
19
|
+
// Validator: bind(LikeC4Validator)
|
|
20
|
+
},
|
|
21
|
+
lsp: {
|
|
22
|
+
DocumentSymbolProvider: bind(LikeC4DocumentSymbolProvider),
|
|
23
|
+
SemanticTokenProvider: bind(LikeC4SemanticTokenProvider),
|
|
24
|
+
HoverProvider: bind(LikeC4HoverProvider),
|
|
25
|
+
},
|
|
26
|
+
//
|
|
27
|
+
// // Formatter: bind(LikeC4Formatter),
|
|
28
|
+
//
|
|
29
|
+
// },
|
|
30
|
+
references: {
|
|
31
|
+
ScopeComputation: bind(LikeC4ScopeComputation),
|
|
32
|
+
ScopeProvider: bind(LikeC4ScopeProvider)
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
const LikeC4SharedModule = {
|
|
36
|
+
...LikeC4GeneratedSharedModule,
|
|
37
|
+
lsp: {
|
|
38
|
+
CodeLensProvider: (services) => new LikeC4CodeLensProvider(services)
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
export function createLanguageServices(context) {
|
|
42
|
+
// const connection = context.connection
|
|
43
|
+
// if (connection) {
|
|
44
|
+
// logger.log = connection.console.log.bind(connection.console)
|
|
45
|
+
// logger.info = connection.console.info.bind(connection.console)
|
|
46
|
+
// logger.warn = connection.console.warn.bind(connection.console)
|
|
47
|
+
// logger.error = connection.console.error.bind(connection.console)
|
|
48
|
+
// logger.debug = connection.tracer.log.bind(connection.tracer)
|
|
49
|
+
// logger.trace = connection.tracer.log.bind(connection.tracer)
|
|
50
|
+
// }
|
|
51
|
+
const shared = inject(createDefaultSharedModule(context), LikeC4SharedModule);
|
|
52
|
+
const likec4 = inject(createDefaultModule({ shared }), LikeC4GeneratedModule, LikeC4Module);
|
|
53
|
+
shared.ServiceRegistry.register(likec4);
|
|
54
|
+
registerValidationChecks(likec4);
|
|
55
|
+
registerProtocolHandlers(likec4);
|
|
56
|
+
return { shared, likec4 };
|
|
57
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Fqn, LikeC4Model, RelationID, ViewID } from '@likec4/core/types';
|
|
2
|
+
import type { Location } from 'vscode-languageserver-protocol';
|
|
3
|
+
import { NotificationType, RequestType0, RequestType } from 'vscode-languageserver-protocol';
|
|
4
|
+
export declare const onDidChangeLikeC4Model: NotificationType<unknown>;
|
|
5
|
+
export declare const fetchLikeC4Model: RequestType0<{
|
|
6
|
+
model: LikeC4Model | null;
|
|
7
|
+
}, unknown>;
|
|
8
|
+
export declare const buildDocuments: RequestType<string[], void, unknown>;
|
|
9
|
+
export declare const locateElement: RequestType<{
|
|
10
|
+
element: Fqn;
|
|
11
|
+
property?: string;
|
|
12
|
+
}, Location | null, unknown>;
|
|
13
|
+
export declare const locateRelation: RequestType<{
|
|
14
|
+
id: RelationID;
|
|
15
|
+
}, Location | null, unknown>;
|
|
16
|
+
export declare const locateView: RequestType<{
|
|
17
|
+
id: ViewID;
|
|
18
|
+
}, Location | null, unknown>;
|
|
19
|
+
export declare const Rpc: {
|
|
20
|
+
readonly onDidChangeModel: NotificationType<unknown>;
|
|
21
|
+
readonly fetchModel: RequestType0<{
|
|
22
|
+
model: LikeC4Model | null;
|
|
23
|
+
}, unknown>;
|
|
24
|
+
readonly buildDocuments: RequestType<string[], void, unknown>;
|
|
25
|
+
readonly locateElement: RequestType<{
|
|
26
|
+
element: Fqn;
|
|
27
|
+
property?: string;
|
|
28
|
+
}, Location | null, unknown>;
|
|
29
|
+
readonly locateRelation: RequestType<{
|
|
30
|
+
id: RelationID;
|
|
31
|
+
}, Location | null, unknown>;
|
|
32
|
+
readonly locateView: RequestType<{
|
|
33
|
+
id: ViewID;
|
|
34
|
+
}, Location | null, unknown>;
|
|
35
|
+
};
|
package/dist/protocol.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { NotificationType, RequestType0, RequestType } from 'vscode-languageserver-protocol';
|
|
2
|
+
//#region From server
|
|
3
|
+
export const onDidChangeLikeC4Model = new NotificationType('likec4/onDidChangeModel');
|
|
4
|
+
//#endregion
|
|
5
|
+
// //#region To server
|
|
6
|
+
export const fetchLikeC4Model = new RequestType0('likec4/fetchModel');
|
|
7
|
+
export const buildDocuments = new RequestType('likec4/buildDocuments');
|
|
8
|
+
export const locateElement = new RequestType('likec4/locateElement');
|
|
9
|
+
export const locateRelation = new RequestType('likec4/locateRelation');
|
|
10
|
+
export const locateView = new RequestType('likec4/locateView');
|
|
11
|
+
// //#endregion
|
|
12
|
+
export const Rpc = {
|
|
13
|
+
onDidChangeModel: onDidChangeLikeC4Model,
|
|
14
|
+
fetchModel: fetchLikeC4Model,
|
|
15
|
+
buildDocuments,
|
|
16
|
+
locateElement,
|
|
17
|
+
locateRelation,
|
|
18
|
+
locateView
|
|
19
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { DefaultScopeComputation, MultiMap, type AstNodeDescription, type PrecomputedScopes } from 'langium';
|
|
2
|
+
import type { CancellationToken } from 'vscode-languageserver-protocol';
|
|
3
|
+
import { ast, type LikeC4LangiumDocument } from '../ast';
|
|
4
|
+
type ElementsContainer = ast.Model | ast.ElementBody | ast.ExtendElementBody;
|
|
5
|
+
export declare class LikeC4ScopeComputation extends DefaultScopeComputation {
|
|
6
|
+
computeExports(document: LikeC4LangiumDocument, _cancelToken: CancellationToken): Promise<AstNodeDescription[]>;
|
|
7
|
+
computeLocalScopes(document: LikeC4LangiumDocument, _cancelToken: CancellationToken): Promise<PrecomputedScopes>;
|
|
8
|
+
protected processContainer(container: ElementsContainer, scopes: PrecomputedScopes, document: LikeC4LangiumDocument): MultiMap<string, AstNodeDescription>;
|
|
9
|
+
}
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { DefaultScopeComputation, MultiMap } from 'langium';
|
|
2
|
+
import { ast } from '../ast';
|
|
3
|
+
export class LikeC4ScopeComputation extends DefaultScopeComputation {
|
|
4
|
+
// constructor(services: LikeC4Services) {
|
|
5
|
+
// super(services)
|
|
6
|
+
// }
|
|
7
|
+
computeExports(document, _cancelToken) {
|
|
8
|
+
const { specification, model, views } = document.parseResult.value;
|
|
9
|
+
const docExports = [];
|
|
10
|
+
if (specification) {
|
|
11
|
+
for (const { kind } of specification.elementKinds) {
|
|
12
|
+
docExports.push(this.descriptions.createDescription(kind, kind.name, document));
|
|
13
|
+
}
|
|
14
|
+
for (const { tag } of specification.tags) {
|
|
15
|
+
docExports.push(this.descriptions.createDescription(tag, tag.name, document));
|
|
16
|
+
docExports.push(this.descriptions.createDescription(tag, '#' + tag.name, document));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (model) {
|
|
20
|
+
for (const elAst of model.elements) {
|
|
21
|
+
if (ast.isElement(elAst)) {
|
|
22
|
+
docExports.push(this.descriptions.createDescription(elAst, elAst.name, document));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (views) {
|
|
27
|
+
for (const viewAst of views.views) {
|
|
28
|
+
if ('name' in viewAst) {
|
|
29
|
+
docExports.push(this.descriptions.createDescription(viewAst, viewAst.name, document));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return Promise.resolve(docExports);
|
|
34
|
+
}
|
|
35
|
+
async computeLocalScopes(document, _cancelToken) {
|
|
36
|
+
const root = document.parseResult.value;
|
|
37
|
+
const scopes = new MultiMap();
|
|
38
|
+
if (root.model) {
|
|
39
|
+
const nested = this.processContainer(root.model, scopes, document);
|
|
40
|
+
scopes.addAll(root, nested.values());
|
|
41
|
+
}
|
|
42
|
+
return Promise.resolve(scopes);
|
|
43
|
+
}
|
|
44
|
+
processContainer(container, scopes, document) {
|
|
45
|
+
const localScope = new MultiMap();
|
|
46
|
+
const nestedScopes = new MultiMap();
|
|
47
|
+
for (const el of container.elements) {
|
|
48
|
+
if (ast.isRelation(el)) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
let subcontainer;
|
|
52
|
+
if (ast.isElement(el)) {
|
|
53
|
+
localScope.add(el.name, this.descriptions.createDescription(el, el.name, document));
|
|
54
|
+
subcontainer = el.body;
|
|
55
|
+
}
|
|
56
|
+
else if (ast.isExtendElement(el)) {
|
|
57
|
+
subcontainer = el.body;
|
|
58
|
+
}
|
|
59
|
+
if (subcontainer && subcontainer.elements.length > 0) {
|
|
60
|
+
const nested = this.processContainer(subcontainer, scopes, document);
|
|
61
|
+
for (const [nestedName, desc] of nested) {
|
|
62
|
+
nestedScopes.add(nestedName, desc);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
for (const [name, descriptions] of nestedScopes.entriesGroupedByKey()) {
|
|
67
|
+
// If name is unique for current scope
|
|
68
|
+
if (!localScope.has(name) && descriptions.length === 1) {
|
|
69
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
70
|
+
localScope.add(name, descriptions[0]);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
scopes.addAll(container, localScope.values());
|
|
74
|
+
return localScope;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AstNode } from 'langium';
|
|
2
|
+
import { DefaultScopeProvider, type ReferenceInfo, type Scope } from 'langium';
|
|
3
|
+
import type { LikeC4Services } from '../module';
|
|
4
|
+
export declare class LikeC4ScopeProvider extends DefaultScopeProvider {
|
|
5
|
+
private fqnIndex;
|
|
6
|
+
constructor(services: LikeC4Services);
|
|
7
|
+
private scopeElementRef;
|
|
8
|
+
private scopeElementView;
|
|
9
|
+
getScope(context: ReferenceInfo): Scope;
|
|
10
|
+
protected computeScope(node: AstNode, referenceType: string): Scope;
|
|
11
|
+
/**
|
|
12
|
+
* Create a global scope filtered for the given reference type.
|
|
13
|
+
*/
|
|
14
|
+
protected getGlobalScope(referenceType: string): Scope;
|
|
15
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { DONE_RESULT, DefaultScopeProvider, EMPTY_SCOPE, StreamImpl, StreamScope, getDocument, stream, EMPTY_STREAM } from 'langium';
|
|
2
|
+
import { ast } from '../ast';
|
|
3
|
+
import { elementRef, isElementRefHead, parentStrictElementRef, strictElementRefFqn } from '../elementRef';
|
|
4
|
+
export class LikeC4ScopeProvider extends DefaultScopeProvider {
|
|
5
|
+
fqnIndex;
|
|
6
|
+
constructor(services) {
|
|
7
|
+
super(services);
|
|
8
|
+
this.fqnIndex = services.likec4.FqnIndex;
|
|
9
|
+
}
|
|
10
|
+
scopeElementRef(ref) {
|
|
11
|
+
const parentNode = ref.$container;
|
|
12
|
+
if (!ast.isElementRef(parentNode)) {
|
|
13
|
+
throw new Error('Expected be inside ElementRef');
|
|
14
|
+
}
|
|
15
|
+
return new StreamImpl(() => {
|
|
16
|
+
const parent = parentNode.el.ref;
|
|
17
|
+
const fqn = parent && this.fqnIndex.get(parent);
|
|
18
|
+
if (fqn) {
|
|
19
|
+
return this.fqnIndex.uniqueDescedants(fqn).iterator();
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}, (iterator) => {
|
|
23
|
+
if (iterator) {
|
|
24
|
+
return iterator.next();
|
|
25
|
+
}
|
|
26
|
+
return DONE_RESULT;
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
scopeElementView({ viewOf }) {
|
|
30
|
+
if (!viewOf) {
|
|
31
|
+
return EMPTY_STREAM;
|
|
32
|
+
}
|
|
33
|
+
return new StreamImpl(() => {
|
|
34
|
+
const target = elementRef(viewOf);
|
|
35
|
+
const fqn = target && this.fqnIndex.get(target);
|
|
36
|
+
if (fqn) {
|
|
37
|
+
return this.fqnIndex.uniqueDescedants(fqn).iterator();
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}, (iterator) => {
|
|
41
|
+
if (iterator) {
|
|
42
|
+
return iterator.next();
|
|
43
|
+
}
|
|
44
|
+
return DONE_RESULT;
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
getScope(context) {
|
|
48
|
+
try {
|
|
49
|
+
const referenceType = this.reflection.getReferenceType(context);
|
|
50
|
+
const node = context.container;
|
|
51
|
+
// const path = this.services.workspace.AstNodeLocator.getAstNodePath(node)
|
|
52
|
+
if (referenceType === ast.Element) {
|
|
53
|
+
if (ast.isStrictElementRef(node)) {
|
|
54
|
+
if (isElementRefHead(node)) {
|
|
55
|
+
return this.getGlobalScope(referenceType);
|
|
56
|
+
}
|
|
57
|
+
const parent = parentStrictElementRef(node);
|
|
58
|
+
return new StreamScope(this.fqnIndex.directChildrenOf(parent));
|
|
59
|
+
}
|
|
60
|
+
if (ast.isElementRef(node) && !isElementRefHead(node)) {
|
|
61
|
+
return new StreamScope(this.scopeElementRef(node));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return this.computeScope(node, referenceType);
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
console.error(e);
|
|
68
|
+
// logger.error(e)
|
|
69
|
+
return EMPTY_SCOPE;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
computeScope(node, referenceType) {
|
|
73
|
+
const scopes = [];
|
|
74
|
+
const doc = getDocument(node);
|
|
75
|
+
const precomputed = doc.precomputedScopes;
|
|
76
|
+
const byReferenceType = (desc) => this.reflection.isSubtype(desc.type, referenceType);
|
|
77
|
+
if (precomputed) {
|
|
78
|
+
const elements = precomputed.get(node).filter(byReferenceType);
|
|
79
|
+
if (elements.length > 0) {
|
|
80
|
+
scopes.push(stream(elements));
|
|
81
|
+
}
|
|
82
|
+
let container = node.$container;
|
|
83
|
+
while (container) {
|
|
84
|
+
const elements = precomputed.get(container).filter(byReferenceType);
|
|
85
|
+
if (elements.length > 0) {
|
|
86
|
+
scopes.push(stream(elements));
|
|
87
|
+
}
|
|
88
|
+
if (referenceType === ast.Element) {
|
|
89
|
+
if (ast.isExtendElementBody(container)) {
|
|
90
|
+
const extendsOf = strictElementRefFqn(container.$container.element);
|
|
91
|
+
scopes.push(this.fqnIndex.uniqueDescedants(extendsOf));
|
|
92
|
+
}
|
|
93
|
+
if (ast.isViewRule(container)) {
|
|
94
|
+
scopes.push(this.scopeElementView(container.$container));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
container = container.$container;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return scopes.reduceRight((outerScope, elements) => {
|
|
101
|
+
return this.createScope(elements, outerScope);
|
|
102
|
+
}, this.getGlobalScope(referenceType));
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Create a global scope filtered for the given reference type.
|
|
106
|
+
*/
|
|
107
|
+
getGlobalScope(referenceType) {
|
|
108
|
+
return new StreamScope(this.indexManager.allElements(referenceType));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { logger } from './logger';
|
|
2
|
+
import { Rpc } from './protocol';
|
|
3
|
+
export function registerProtocolHandlers(services) {
|
|
4
|
+
const connection = services.shared.lsp.Connection;
|
|
5
|
+
if (!connection) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
const modelBuilder = services.likec4.ModelBuilder;
|
|
9
|
+
const modelLocator = services.likec4.ModelLocator;
|
|
10
|
+
const LangiumDocuments = services.shared.workspace.LangiumDocuments;
|
|
11
|
+
connection.onRequest(Rpc.fetchModel, async (_cancelToken) => {
|
|
12
|
+
let model;
|
|
13
|
+
try {
|
|
14
|
+
model = modelBuilder.buildModel() ?? null;
|
|
15
|
+
}
|
|
16
|
+
catch (e) {
|
|
17
|
+
model = null;
|
|
18
|
+
logger.error(e);
|
|
19
|
+
}
|
|
20
|
+
return Promise.resolve({
|
|
21
|
+
model: model ?? null
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
connection.onRequest(Rpc.buildDocuments, async (docs, cancelToken) => {
|
|
25
|
+
const changed = [];
|
|
26
|
+
for (const d of docs) {
|
|
27
|
+
const uri = d;
|
|
28
|
+
if (LangiumDocuments.hasDocument(uri)) {
|
|
29
|
+
changed.push(uri);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
logger.error(`LangiumDocuments does not have document: ${uri.toString()}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
logger.debug(`Received request to rebuild: [
|
|
36
|
+
${changed.map(d => d.toString()).join('\n ')}
|
|
37
|
+
]`);
|
|
38
|
+
await services.shared.workspace.DocumentBuilder.update(changed, [], cancelToken);
|
|
39
|
+
});
|
|
40
|
+
connection.onRequest(Rpc.locateElement, ({ element, property }, _cancelToken) => {
|
|
41
|
+
return modelLocator.locateElement(element, property);
|
|
42
|
+
});
|
|
43
|
+
connection.onRequest(Rpc.locateRelation, ({ id }, _cancelToken) => {
|
|
44
|
+
return modelLocator.locateRelation(id);
|
|
45
|
+
});
|
|
46
|
+
connection.onRequest(Rpc.locateView, ({ id }, _cancelToken) => {
|
|
47
|
+
return modelLocator.locateView(id);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './testServices';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './testServices';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { LikeC4LangiumDocument } from '../ast';
|
|
2
|
+
export declare function createTestServices(): {
|
|
3
|
+
services: import("../module").LikeC4Services;
|
|
4
|
+
parse: (input: string, uri?: string) => Promise<LikeC4LangiumDocument>;
|
|
5
|
+
validate: (input: string | LikeC4LangiumDocument) => Promise<{
|
|
6
|
+
document: LikeC4LangiumDocument;
|
|
7
|
+
diagnostics: import("vscode-languageserver-types").Diagnostic[];
|
|
8
|
+
errors: string[];
|
|
9
|
+
}>;
|
|
10
|
+
validateAll: () => Promise<{
|
|
11
|
+
diagnostics: import("vscode-languageserver-types").Diagnostic[];
|
|
12
|
+
errors: string[];
|
|
13
|
+
}>;
|
|
14
|
+
buildModel: () => Promise<import("@likec4/core/types").LikeC4Model>;
|
|
15
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { createLanguageServices } from '../module';
|
|
2
|
+
import { EmptyFileSystem } from 'langium';
|
|
3
|
+
import { URI } from 'vscode-uri';
|
|
4
|
+
export function createTestServices() {
|
|
5
|
+
const services = createLanguageServices(EmptyFileSystem).likec4;
|
|
6
|
+
const metaData = services.LanguageMetaData;
|
|
7
|
+
const langiumDocuments = services.shared.workspace.LangiumDocuments;
|
|
8
|
+
const documentBuilder = services.shared.workspace.DocumentBuilder;
|
|
9
|
+
const modelBuilder = services.likec4.ModelBuilder;
|
|
10
|
+
let documentIndex = 1;
|
|
11
|
+
const parse = async (input, uri) => {
|
|
12
|
+
uri = uri ?? `${documentIndex++}${metaData.fileExtensions[0]}`;
|
|
13
|
+
const document = services.shared.workspace.LangiumDocumentFactory.fromString(input, URI.file(uri));
|
|
14
|
+
langiumDocuments.addDocument(document);
|
|
15
|
+
await documentBuilder.build([document], { validationChecks: 'none' });
|
|
16
|
+
return document;
|
|
17
|
+
};
|
|
18
|
+
const validate = async (input) => {
|
|
19
|
+
const document = typeof input === 'string' ? await parse(input) : input;
|
|
20
|
+
await documentBuilder.build([document], { validationChecks: 'all' });
|
|
21
|
+
const diagnostics = document.diagnostics ?? [];
|
|
22
|
+
const errors = diagnostics.map(d => d.message);
|
|
23
|
+
return {
|
|
24
|
+
document,
|
|
25
|
+
diagnostics,
|
|
26
|
+
errors
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
const validateAll = async () => {
|
|
30
|
+
const docs = langiumDocuments.all.toArray();
|
|
31
|
+
await documentBuilder.build(docs, { validationChecks: 'all' });
|
|
32
|
+
const diagnostics = docs.flatMap(doc => doc.diagnostics ?? []);
|
|
33
|
+
const errors = diagnostics.map(d => d.message);
|
|
34
|
+
return {
|
|
35
|
+
diagnostics,
|
|
36
|
+
errors
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
const buildModel = async () => {
|
|
40
|
+
await validateAll();
|
|
41
|
+
const model = modelBuilder.buildModel();
|
|
42
|
+
if (!model)
|
|
43
|
+
throw new Error('No model found');
|
|
44
|
+
return model;
|
|
45
|
+
};
|
|
46
|
+
return {
|
|
47
|
+
services,
|
|
48
|
+
parse,
|
|
49
|
+
validate,
|
|
50
|
+
validateAll,
|
|
51
|
+
buildModel
|
|
52
|
+
};
|
|
53
|
+
}
|
package/dist/utils.d.ts
ADDED
package/dist/utils.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export function failExpectedNever(arg) {
|
|
2
|
+
throw new Error(`Unexpected value: ${JSON.stringify(arg)}`);
|
|
3
|
+
}
|
|
4
|
+
export function ignoreNeverInRuntime(arg) {
|
|
5
|
+
console.warn(`Unexpected and ignored value: ${JSON.stringify(arg)}`);
|
|
6
|
+
// throw new Error(`Unexpected value: ${arg}`);
|
|
7
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const elementChecks = (services) => {
|
|
2
|
+
const fqnIndex = services.likec4.FqnIndex;
|
|
3
|
+
return (el, accept) => {
|
|
4
|
+
const fqn = fqnIndex.get(el);
|
|
5
|
+
if (!fqn) {
|
|
6
|
+
accept('error', 'Not indexed', {
|
|
7
|
+
node: el,
|
|
8
|
+
property: 'name',
|
|
9
|
+
});
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const withSameFqn = fqnIndex.byFqn(fqn);
|
|
13
|
+
if (withSameFqn.length > 1) {
|
|
14
|
+
accept('error', `Duplicate element name ${el.name !== fqn ? el.name + ' (' + fqn + ')' : el.name}`, {
|
|
15
|
+
node: el,
|
|
16
|
+
property: 'name',
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { elementChecks } from './element';
|
|
2
|
+
import { elementKindChecks, tagChecks } from './specification';
|
|
3
|
+
import { viewChecks } from './view';
|
|
4
|
+
export function registerValidationChecks(services) {
|
|
5
|
+
const registry = services.validation.ValidationRegistry;
|
|
6
|
+
// const checks: ValidationChecks = {
|
|
7
|
+
// Element: validator.checkElementNameDuplicates,
|
|
8
|
+
// Tag: validator.checkTagDuplicates,
|
|
9
|
+
// ElementKind: elementKindChecks(services),
|
|
10
|
+
// ElementStyleProperty: validator.checkElementStyleProperty,
|
|
11
|
+
// View: validator.checkViewNameDuplicates,
|
|
12
|
+
// ColorStyleProperty: validator.checkColorStyleProperty,
|
|
13
|
+
// }
|
|
14
|
+
registry.register({
|
|
15
|
+
ElementView: viewChecks(services),
|
|
16
|
+
Element: elementChecks(services),
|
|
17
|
+
ElementKind: elementKindChecks(services),
|
|
18
|
+
Tag: tagChecks(services),
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -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 elementKindChecks: (services: LikeC4Services) => ValidationCheck<ast.ElementKind>;
|
|
5
|
+
export declare const tagChecks: (services: LikeC4Services) => ValidationCheck<ast.Tag>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ast } from '../ast';
|
|
2
|
+
export const elementKindChecks = (services) => {
|
|
3
|
+
const index = services.shared.workspace.IndexManager;
|
|
4
|
+
return (node, accept) => {
|
|
5
|
+
const sameKinds = index.allElements(ast.ElementKind)
|
|
6
|
+
.filter(n => n.name === node.name)
|
|
7
|
+
.limit(2)
|
|
8
|
+
.count();
|
|
9
|
+
if (sameKinds > 1) {
|
|
10
|
+
accept('error', `Duplicate element kind '${node.name}'`, {
|
|
11
|
+
node: node,
|
|
12
|
+
property: 'name',
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
export const tagChecks = (services) => {
|
|
18
|
+
const index = services.shared.workspace.IndexManager;
|
|
19
|
+
return (node, accept) => {
|
|
20
|
+
const sameKinds = index.allElements(ast.Tag)
|
|
21
|
+
.filter(n => n.name === node.name)
|
|
22
|
+
.limit(2)
|
|
23
|
+
.count();
|
|
24
|
+
if (sameKinds > 1) {
|
|
25
|
+
accept('error', `Duplicate tag '${node.name}'`, {
|
|
26
|
+
node: node,
|
|
27
|
+
property: 'name',
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ast } from '../ast';
|
|
2
|
+
export const viewChecks = (services) => {
|
|
3
|
+
const index = services.shared.workspace.IndexManager;
|
|
4
|
+
return (el, accept) => {
|
|
5
|
+
if (!el.name) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
const anotherViews = index.allElements(ast.View)
|
|
9
|
+
.filter(n => n.name === el.name)
|
|
10
|
+
.limit(2)
|
|
11
|
+
.count();
|
|
12
|
+
if (anotherViews > 1) {
|
|
13
|
+
accept('error', `Duplicate view '${el.name}'`, {
|
|
14
|
+
node: el,
|
|
15
|
+
property: 'name'
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
};
|