@lionweb/validation 0.5.0-beta.8

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.
@@ -0,0 +1,68 @@
1
+ import { JsonContext, ValidationIssue } from "./ValidationIssue";
2
+
3
+ export abstract class Syntax_PropertyIssue extends ValidationIssue {
4
+ constructor(public context: JsonContext, protected property: string) {
5
+ super(context);
6
+ }
7
+ }
8
+
9
+ export class Syntax_PropertyMissingIssue extends Syntax_PropertyIssue {
10
+ readonly id = 'PropertyMissing';
11
+ protected msg = () => `Property "${this.property}" is missing`;
12
+ }
13
+
14
+ export class Syntax_PropertyUnknownIssue extends Syntax_PropertyIssue {
15
+ readonly id = 'PropertyUnknown';
16
+ protected msg = () => `Property "${this.property}" is not defined as a LionWeb property`;
17
+ }
18
+
19
+ export class Syntax_PropertyNullIssue extends Syntax_PropertyIssue {
20
+ readonly id = 'PropertyNull';
21
+ protected msg = () => `Property "${this.property}" is null, but it should have a value`;
22
+ }
23
+
24
+ export class Syntax_PropertyTypeIssue extends Syntax_PropertyIssue {
25
+ readonly id = 'PropertyTypeIncorrect';
26
+
27
+ constructor(context: JsonContext, property: string, protected expectedType: string, protected actualType: string) {
28
+ super(context, property);
29
+ }
30
+
31
+ protected msg = () => `Property ${this.property} should have type "${this.expectedType}", but has type "${this.actualType}"`;
32
+ }
33
+
34
+ export class Syntax_ArrayContainsNull_Issue extends Syntax_PropertyIssue {
35
+ readonly id = "ArrayContainsNull";
36
+
37
+ constructor(context: JsonContext, property: string, public index: number) {
38
+ super(context, property);
39
+ }
40
+
41
+ protected msg = () => `Property "${this.property}" of type array contains null at index "${"" + this.index}" `
42
+ }
43
+
44
+ export abstract class Syntax_IncorrectFormat_Issue extends ValidationIssue {
45
+ constructor(context: JsonContext, public value: string) {
46
+ super(context);
47
+ }
48
+ }
49
+
50
+ export class Syntax_SerializationFormatVersion_Issue extends Syntax_IncorrectFormat_Issue {
51
+ readonly id = "SerializationFormatVersion";
52
+ protected msg = () => `SerializationFormatVersion "${this.value}" is not a number`
53
+ }
54
+
55
+ export class Syntax_VersionFormat_Issue extends Syntax_IncorrectFormat_Issue {
56
+ readonly id = "VersionFormat";
57
+ protected msg = () => `Version "${this.value}" is an empty string.`
58
+ }
59
+
60
+ export class Syntax_KeyFormat_Issue extends Syntax_IncorrectFormat_Issue {
61
+ readonly id = "KeyFormat";
62
+ protected msg = () => `Key "${this.value}" has incorrect format.`
63
+ }
64
+
65
+ export class Syntax_IdFormat_Issue extends Syntax_IncorrectFormat_Issue {
66
+ readonly id = "IdFormat";
67
+ protected msg = () => `Id "${this.value}" has incorrect format.`
68
+ }
@@ -0,0 +1,46 @@
1
+ type JsonPath = (string | number)[];
2
+
3
+ export class JsonContext {
4
+ private parent: JsonContext | null;
5
+ private local_path: JsonPath;
6
+
7
+ concat(... items: JsonPath) : JsonContext {
8
+ return new JsonContext(this, items);
9
+ }
10
+
11
+ path(): JsonPath {
12
+ return this.parent === null ? this.local_path : this.parent.path().concat(this.local_path);
13
+ }
14
+
15
+ constructor(parent: JsonContext | null, path: JsonPath) {
16
+ this.parent = parent;
17
+ this.local_path = path;
18
+ }
19
+
20
+ toString(): string {
21
+ let result = "";
22
+ this.path().forEach((part, index) => {
23
+ if (typeof part === "string") {
24
+ result += (index === 0 ? "" : ".") + part;
25
+ } else if (typeof part === "number") {
26
+ result += "[" + part + "]";
27
+ }
28
+ });
29
+ return result;
30
+ }
31
+ }
32
+
33
+ export abstract class ValidationIssue {
34
+ abstract readonly id: string;
35
+ context: JsonContext;
36
+
37
+ constructor(context: JsonContext) {
38
+ this.context = context;
39
+ }
40
+
41
+ protected abstract msg(): string;
42
+
43
+ public errorMsg(): string {
44
+ return `${this.id}: ${this.msg()} at ${this.context.toString()} `
45
+ }
46
+ }
@@ -0,0 +1,53 @@
1
+ import {
2
+ LionWebJsonChunk,
3
+ LionWebJsonNode,
4
+ LwJsonUsedLanguage
5
+ } from "./LionWebJson";
6
+
7
+ /**
8
+ * Utility functions for LionWeb chunks
9
+ */
10
+ export class ChunkUtils {
11
+ /**
12
+ * Find a used language in `chunk` with `key`.
13
+ * @param chunk
14
+ * @param key
15
+ */
16
+ static findLwUsedLanguage(chunk: LionWebJsonChunk, key: string): LwJsonUsedLanguage | null {
17
+ for (const language of chunk.languages) {
18
+ if (language.key === key) {
19
+ return language;
20
+ }
21
+ }
22
+ return null;
23
+ }
24
+
25
+ /**
26
+ * Find a used language in `chunk` with `key` and 'version'.
27
+ * @param chunk
28
+ * @param key
29
+ * @param version
30
+ */
31
+ static findLwUsedLanguageWithVersion(chunk: LionWebJsonChunk, key: string, version: string): LwJsonUsedLanguage | null {
32
+ for (const language of chunk.languages) {
33
+ if (language.key === key && language.version === version) {
34
+ return language;
35
+ }
36
+ }
37
+ return null;
38
+ }
39
+
40
+ /**
41
+ * Find node with id equals `id` in `chunk`.
42
+ * @param chunk
43
+ * @param id
44
+ */
45
+ static findNode(chunk: LionWebJsonChunk, id: string): LionWebJsonNode | null {
46
+ for (const node of chunk.nodes) {
47
+ if (node.id === id) {
48
+ return node;
49
+ }
50
+ }
51
+ return null;
52
+ }
53
+ }
@@ -0,0 +1,12 @@
1
+ import { LionWebJsonChunk } from "./LionWebJson";
2
+ import { LionWebJsonChunkWrapper } from "./LionWebJsonChunkWrapper";
3
+
4
+ /**
5
+ * Contains methods for getting information from a LionWebJsonChunk representing a language.
6
+ */
7
+ export class LanguageWrapper extends LionWebJsonChunkWrapper {
8
+
9
+ constructor(languageJson: LionWebJsonChunk) {
10
+ super(languageJson);
11
+ }
12
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * The types defining the structure of the LionWeb JSON format.
3
+ * @see https://lionweb-io.github.io/specification/serialization/serialization.html
4
+ * We use types instead of classes, because the purpose is to define the Lionweb JSON to be sent over the line.
5
+ */
6
+ export const LION_CORE_BUILTINS_KEY = "LionCore-builtins";
7
+ export const LION_CORE_BUILTINS_INAMED_NAME = "LionCore-builtins-INamed-name";
8
+
9
+ export const LIONWEB_BOOLEAN_TYPE = "LionCore-builtins-Boolean";
10
+ export const LIONWEB_JSON_TYPE = "LionCore-builtins-JSON";
11
+ export const LIONWEB_INTEGER_TYPE = "LionCore-builtins-Integer";
12
+ export const LIONWEB_STRING_TYPE = "LionCore-builtins-String";
13
+
14
+ export type Id = string;
15
+
16
+ export type LionWebJsonMetaPointer = {
17
+ language: string;
18
+ version: string;
19
+ key: string;
20
+ };
21
+
22
+ export function isEqualMetaPointer(p1: LionWebJsonMetaPointer, p2: LionWebJsonMetaPointer): boolean {
23
+ return p1.key === p2.key && p1.version === p2.version && p1.language === p2.language;
24
+ }
25
+
26
+ export type LionWebJsonChunk = {
27
+ serializationFormatVersion: string;
28
+ languages: LwJsonUsedLanguage[];
29
+ nodes: LionWebJsonNode[];
30
+ };
31
+
32
+ export type LwJsonUsedLanguage = {
33
+ key: string;
34
+ version: string;
35
+ };
36
+
37
+ export type LionWebJsonNode = {
38
+ id: Id;
39
+ classifier: LionWebJsonMetaPointer;
40
+ properties: LionWebJsonProperty[];
41
+ containments: LionWebJsonChild[];
42
+ references: LionWebJsonReference[];
43
+ annotations: Id[];
44
+ parent: Id | null;
45
+ };
46
+
47
+ export function createLwNode(): LionWebJsonNode {
48
+ return {
49
+ id: "",
50
+ classifier: { language: "", version: "", key: "" },
51
+ properties: [],
52
+ containments: [],
53
+ references: [],
54
+ annotations: [],
55
+ parent: null,
56
+ };
57
+ }
58
+
59
+ export type LionWebJsonProperty = {
60
+ property: LionWebJsonMetaPointer;
61
+ value: string;
62
+ };
63
+
64
+ export type LionWebJsonChild = {
65
+ containment: LionWebJsonMetaPointer;
66
+ children: string[];
67
+ };
68
+
69
+ export type LionWebJsonReference = {
70
+ reference: LionWebJsonMetaPointer;
71
+ targets: LionWebJsonReferenceTarget[];
72
+ };
73
+
74
+ export type LionWebJsonReferenceTarget = {
75
+ resolveInfo: string;
76
+ reference: string;
77
+ };
@@ -0,0 +1,74 @@
1
+ import { LionWebJsonChunk, LionWebJsonNode, LionWebJsonProperty } from "./LionWebJson";
2
+ import { LionWebLanguageDefinition } from "./LionWebLanguageDefinition";
3
+ import { NodeUtils } from "./NodeUtils";
4
+
5
+ export type NodeId = string;
6
+
7
+ /**
8
+ * Wraps around a LionWebJsonChunk, providing access to information inside, using caches to improve performance.
9
+ */
10
+ export class LionWebJsonChunkWrapper {
11
+ jsonChunk: LionWebJsonChunk;
12
+ language: LionWebLanguageDefinition | null = null;
13
+ /**
14
+ * Map to get quick access to nodes by id.
15
+ * @protected
16
+ */
17
+ protected nodesIdMap: Map<NodeId, LionWebJsonNode> = new Map<NodeId, LionWebJsonNode>();
18
+
19
+ constructor(chunk: unknown) {
20
+ this.jsonChunk = chunk as LionWebJsonChunk;
21
+ // this.prepareNodeIds();
22
+ }
23
+
24
+ /** Put all nodes in a map, validate that there are no two nodes with the same id.
25
+ * The check should logically be in LionWebReferenceValidator, but the created map is needed here.
26
+ */
27
+ prepareNodeIds() {
28
+ this.nodesIdMap = new Map<NodeId, LionWebJsonNode>();
29
+ this.jsonChunk.nodes.forEach((node) => {
30
+ this.nodesIdMap.set(node.id, node);
31
+ });
32
+ }
33
+
34
+ getMap(): Map<NodeId, LionWebJsonNode> {
35
+ if (this.nodesIdMap === undefined) {
36
+ this.prepareNodeIds();
37
+ }
38
+ return this.nodesIdMap;
39
+ }
40
+
41
+ getNode(id: string): LionWebJsonNode | undefined {
42
+ return this.getMap().get(id);
43
+ }
44
+
45
+ findNodesOfConcept(conceptKey: string): LionWebJsonNode[] {
46
+ const result: LionWebJsonNode[] = [];
47
+ for (const node of this.jsonChunk.nodes) {
48
+ if (node.classifier.key === conceptKey) {
49
+ result.push(node);
50
+ }
51
+ }
52
+ return result;
53
+ // return this.jsonChunk.nodes.filter(node => node.classifier.key === conceptKey);
54
+ }
55
+
56
+ allProperties(conceptNode: LionWebJsonNode): LionWebJsonProperty[] {
57
+ const result: LionWebJsonProperty[] = [];
58
+ result.push(...conceptNode.properties);
59
+ const extendsReference = NodeUtils.findLwReference(conceptNode, "Concept-extends");
60
+ if (extendsReference !==null) {
61
+ // extendsReference.targets.forEach(target => {
62
+ // // Find the extended concept
63
+ // if (this.language !== null) {
64
+ // // const targetNode = this.language.languageChunkWrapper.getNode(target.reference);
65
+ // // TODO etc., but need to cleanup LanguageDefinition first.
66
+ // }
67
+ // });
68
+ }
69
+ return result;
70
+ }
71
+
72
+ }
73
+
74
+
@@ -0,0 +1,141 @@
1
+ import {
2
+ LION_CORE_BUILTINS_INAMED_NAME,
3
+ LionWebJsonMetaPointer,
4
+ LionWebJsonNode,
5
+ } from "./LionWebJson";
6
+ import { LionWebJsonChunkWrapper } from "./LionWebJsonChunkWrapper";
7
+ import { NodeUtils } from "./NodeUtils";
8
+
9
+ type LanguageId = {
10
+ name?: string;
11
+ version?: string;
12
+ key?: string;
13
+ };
14
+
15
+ export const LIONWEB_M3_PROPERTY_KEY = "Property";
16
+ export const LIONWEB_M3_CONCEPT_KEY = "Concept";
17
+ export const LIONWEB_M3_REFERENCE_KEY = "Reference";
18
+ export const LIONWEB_M3_PROPERTY_TYPE_KEY = "Property-type";
19
+ /**
20
+ * Collection of language definitions
21
+ */
22
+ // export class LionwebLanguages {
23
+ // // The builtin language of LionWeb.
24
+ // static M3 = LionCore_M3 as LionWebJsonChunk;
25
+ //
26
+ // languageMap = new Map<string, Map<string, Map<string, LionwebLanguageDefinition>>>();
27
+ //
28
+ // setLanguage(lionWebLanguage: LionwebLanguageDefinition) {
29
+ // // Assume LwNode is a concept of type "Language"
30
+ // let language = this.languageMap.get(lionWebLanguage.languageId.name);
31
+ // if (language === undefined) {
32
+ // language = new Map<string, Map<string, LionwebLanguageDefinition>>();
33
+ // this.languageMap.set(lionWebLanguage.languageId.name, language);
34
+ // }
35
+ // let version = language.get(lionWebLanguage.languageId.version);
36
+ // if (version === undefined) {
37
+ // version = new Map<string, LionwebLanguageDefinition>();
38
+ // language.set(lionWebLanguage.languageId.version, version);
39
+ // }
40
+ // let key = version.get(lionWebLanguage.languageId.key);
41
+ // if (key === undefined) {
42
+ // version.set(lionWebLanguage.languageId.key, lionWebLanguage);
43
+ // }
44
+ // }
45
+ //
46
+ // getLanguage(pointer: LionWebJsonMetaPointer): LionwebLanguageDefinition {
47
+ // return this.languageMap.get(pointer.language)?.get(pointer.version)?.get(pointer.key);
48
+ // }
49
+ // }
50
+
51
+ /**
52
+ * Represents a LionWeb serialiation chunk which represents a language definition / metamodel
53
+ */
54
+ export class LionWebLanguageDefinition {
55
+ languageId: LanguageId | null = null;
56
+ /**
57
+ * All nodes in the language
58
+ */
59
+ nodeKeymap = new Map<string, LionWebJsonNode>();
60
+ languageChunkWrapper: LionWebJsonChunkWrapper;
61
+
62
+ /**
63
+ * Assume chunk represents a language metamodel according to Lionweb M3.
64
+ * @param chunk
65
+ */
66
+ constructor(chunk: LionWebJsonChunkWrapper) {
67
+ // console.log("CHUNK " + JSON.stringify(chunk))
68
+ const languageNodes = chunk.findNodesOfConcept("Language");
69
+ if (languageNodes.length !== 1) {
70
+ // TODO Better error handling.
71
+ console.error("1 Expected exactly one Language node, found " + languageNodes.length + " => " + JSON.stringify(languageNodes));
72
+ } else {
73
+ const languageNode = languageNodes[0];
74
+ this.setLanguage(languageNode);
75
+ }
76
+ this.languageChunkWrapper = chunk;
77
+
78
+ }
79
+
80
+ protected setLanguage(languageNode: LionWebJsonNode) {
81
+ // Assume LwNode is a concept of type "Language"
82
+ const nameProp = languageNode.properties.find(prop => prop.property.key === LION_CORE_BUILTINS_INAMED_NAME);
83
+ const versionProp = languageNode.properties.find(prop => prop.property.key === "Language-version");
84
+ const keyProp = languageNode.properties.find(prop => prop.property.key === "IKeyed-key");
85
+ this.languageId = {
86
+ name: nameProp?.value,
87
+ version: versionProp?.value,
88
+ key: keyProp?.value
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Store the node `node` under `key`.
94
+ * @param key
95
+ * @param node
96
+ */
97
+ setNodeByKey(key: string, node: LionWebJsonNode): void {
98
+ this.nodeKeymap.set(key, node);
99
+ }
100
+
101
+ getNodeByKey(key: string): LionWebJsonNode | undefined {
102
+ return this.nodeKeymap.get(key);
103
+ }
104
+
105
+ getNodeByMetaPointer(metaPointer: LionWebJsonMetaPointer): LionWebJsonNode | undefined {
106
+ // console.log("get metapointer " + JSON.stringify(metaPointer))
107
+ const result = this.languageChunkWrapper.jsonChunk.nodes.find(node => {
108
+ const keyProp = NodeUtils.findLwProperty(node, "IKeyed-key");
109
+ // const versionProp = NodeUtils.findLwProperty(node, "Language-version");
110
+ // return ((!!keyProp) && (keyProp.value === metaPointer.key) && (!!versionProp) && (versionProp.value === metaPointer.version));
111
+
112
+ // console.log(" getNodeByMetaPointer.looking into " + node.id + " found " + JSON.stringify(keyProp));
113
+ return ((!!keyProp) && (keyProp.value === metaPointer.key) );
114
+ });
115
+ return result;
116
+ }
117
+
118
+ getPropertyByKey(key: string) {
119
+ // console.log("get property by key " + key)
120
+ const propertyNode = this.languageChunkWrapper.findNodesOfConcept("Property").find(n => {
121
+ // console.log(" getPropertyByKey.looking into " + n.id);
122
+ const keyProp = (n as LionWebJsonNode).properties.find(prop => {
123
+ return prop.property.key === "IKeyed-key" && prop.value === key;
124
+ });
125
+ return keyProp;
126
+ });
127
+ return propertyNode;
128
+ }
129
+
130
+ getConceptByKey(key: string) {
131
+ // console.log("get property by key " + key)
132
+ const conceptNode = this.languageChunkWrapper.findNodesOfConcept("Concept").find(n => {
133
+ // console.log(" looking into " + JSON.stringify(n));
134
+ const keyProp = (n as LionWebJsonNode).properties.find(prop => {
135
+ return prop.property.key === "IKeyed-key" && prop.value === key;
136
+ });
137
+ return keyProp;
138
+ });
139
+ return conceptNode;
140
+ }
141
+ }
@@ -0,0 +1,72 @@
1
+ import {
2
+ LionWebJsonChild,
3
+ LionWebJsonNode,
4
+ LionWebJsonProperty,
5
+ LionWebJsonReference,
6
+ LionWebJsonReferenceTarget
7
+ } from "./LionWebJson";
8
+
9
+ /**
10
+ * Utility functions for LionWebJsonNode's
11
+ */
12
+ export class NodeUtils {
13
+ /**
14
+ * Find property with key equals `key` in `node`.
15
+ * @param node
16
+ * @param key
17
+ */
18
+ static findLwProperty(node: LionWebJsonNode, key: string): LionWebJsonProperty | null {
19
+ for (const property of node.properties) {
20
+ if (property.property.key === key) {
21
+ return property;
22
+ }
23
+ }
24
+ return null;
25
+ }
26
+
27
+ /**
28
+ * Find containment child with key equals `key` in `node`.
29
+ * @param node
30
+ * @param key
31
+ */
32
+ static findLwChild(node: LionWebJsonNode, key: string): LionWebJsonChild | null {
33
+ for (const containment of node.containments) {
34
+ if (containment.containment.key === key) {
35
+ return containment;
36
+ }
37
+ }
38
+ return null;
39
+ }
40
+
41
+ static findLwReference(node: LionWebJsonNode, key: string): LionWebJsonReference | null {
42
+ for (const reference of node.references) {
43
+ if (reference.reference.key === key) {
44
+ return reference;
45
+ }
46
+ }
47
+ return null;
48
+ }
49
+
50
+ static findLwReferenceTarget(lwReferenceTargets: LionWebJsonReferenceTarget[], target: LionWebJsonReferenceTarget): LionWebJsonReferenceTarget | null {
51
+ for (const refTarget of lwReferenceTargets) {
52
+ if (refTarget.reference === target.reference && refTarget.resolveInfo === target.resolveInfo) {
53
+ return refTarget;
54
+ }
55
+ }
56
+ return null;
57
+ }
58
+
59
+ /**
60
+ * Get all nodes that are children for `node`: both the containment and annotaion children
61
+ * @param node
62
+ */
63
+ static allChildren(node: LionWebJsonNode): string[] {
64
+ const result: string[] = [];
65
+ for (const containment of node.containments) {
66
+ result.push(...containment.children);
67
+ }
68
+ result.push(...node.annotations);
69
+ return result;
70
+ }
71
+
72
+ }