@lionweb/validation 0.7.0-beta.18 → 0.7.0-beta.19

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.
Files changed (38) hide show
  1. package/CHANGELOG.md +0 -1
  2. package/package.json +4 -4
  3. package/src/index.ts +4 -0
  4. package/src/issues/LanguageIssues.ts +147 -0
  5. package/src/issues/ReferenceIssues.ts +83 -0
  6. package/src/issues/SyntaxIssues.ts +84 -0
  7. package/src/issues/ValidationIssue.ts +29 -0
  8. package/src/issues/index.ts +4 -0
  9. package/src/languages/CompositeLionWebLanguageWrapper.ts +57 -0
  10. package/src/languages/LanguageRegistry.ts +44 -0
  11. package/src/languages/LanguageUtils.ts +63 -0
  12. package/src/languages/LionCore-M3.json +2356 -0
  13. package/src/languages/LionCore-builtins.json +372 -0
  14. package/src/languages/LionWebLanguageWrapper.ts +91 -0
  15. package/src/languages/MetaPointerMap.ts +41 -0
  16. package/src/languages/index.ts +2 -0
  17. package/src/runners/FileUtils.ts +59 -0
  18. package/src/runners/RunCheckFolder.ts +7 -0
  19. package/src/runners/RunCheckFolderWithLanguage.ts +45 -0
  20. package/src/runners/RunCheckOneFile.ts +7 -0
  21. package/src/runners/RunCheckOneFileWithLanguage.ts +35 -0
  22. package/src/runners/RunLioncoreDiff.ts +23 -0
  23. package/src/runners/Utils.ts +54 -0
  24. package/src/runners/index.ts +2 -0
  25. package/src/validators/LionWebChunkDefinitions.ts +104 -0
  26. package/src/validators/LionWebLanguageReferenceValidator.ts +201 -0
  27. package/src/validators/LionWebLanguageValidator.ts +79 -0
  28. package/src/validators/LionWebReferenceValidator.ts +225 -0
  29. package/src/validators/LionWebSyntaxValidator.ts +14 -0
  30. package/src/validators/LionWebValidator.ts +71 -0
  31. package/src/validators/ValidationFunctions.ts +129 -0
  32. package/src/validators/generic/SyntaxValidator.ts +164 -0
  33. package/src/validators/generic/ValidationResult.ts +17 -0
  34. package/src/validators/generic/index.ts +3 -0
  35. package/src/validators/generic/schema/DefinitionSchema.ts +52 -0
  36. package/src/validators/generic/schema/ValidationTypes.ts +134 -0
  37. package/src/validators/generic/schema/index.ts +2 -0
  38. package/src/validators/index.ts +8 -0
@@ -0,0 +1,23 @@
1
+ import { LionWebJsonChunk } from "@lionweb/json"
2
+ import { LionWebJsonDiff } from "@lionweb/json-diff"
3
+ import fs from "fs"
4
+
5
+ const file1 = process.argv[2]
6
+ const file2 = process.argv[3]
7
+
8
+ if (file1 !== null && file1 !== undefined) {
9
+ const jsonString1 = fs.readFileSync(file1, "utf-8")
10
+ const json1 = JSON.parse(jsonString1)
11
+ const jsonString2 = fs.readFileSync(file2, "utf-8")
12
+ const json2 = JSON.parse(jsonString2)
13
+
14
+ const lwDiff = new LionWebJsonDiff()
15
+ lwDiff.diffLwChunk(json1 as LionWebJsonChunk, json2 as LionWebJsonChunk)
16
+ if (lwDiff.diffResult.changes.length === 0) {
17
+ console.log("LionWebJsonDiff: equal")
18
+ } else {
19
+ console.log("LionWebJsonDiff: " + lwDiff.diff)
20
+ }
21
+ } else {
22
+ console.log("Error in arguments")
23
+ }
@@ -0,0 +1,54 @@
1
+ import fs from "fs"
2
+ import path from "path"
3
+ import { ValidationResult } from "../validators/generic/ValidationResult.js"
4
+
5
+ export function getFilesRecursive(dirPath: string, arrayOfFiles: string[]) {
6
+ const files = fs.readdirSync(dirPath)
7
+ arrayOfFiles = arrayOfFiles || []
8
+
9
+ files.forEach(function (file: string) {
10
+ if (fs.statSync(dirPath + "/" + file).isDirectory()) {
11
+ arrayOfFiles = getFilesRecursive(dirPath + "/" + file, arrayOfFiles)
12
+ } else {
13
+ arrayOfFiles.push(path.join(dirPath, "/", file))
14
+ }
15
+ })
16
+ return arrayOfFiles
17
+ }
18
+
19
+ export function getFilesDirect(dirPath: string, arrayOfFiles: string[]) {
20
+ const files = fs.readdirSync(dirPath)
21
+ arrayOfFiles = arrayOfFiles || []
22
+
23
+ files.forEach(function (file: string) {
24
+ if (fs.statSync(dirPath + "/" + file).isFile()) {
25
+ arrayOfFiles.push(file)
26
+ }
27
+ })
28
+ return arrayOfFiles
29
+ }
30
+
31
+ export function getAllDirectories(dirPath: string, arrayOfDirs: string[]) {
32
+ const files = fs.readdirSync(dirPath, { withFileTypes: true })
33
+ arrayOfDirs = arrayOfDirs || []
34
+
35
+ files.forEach(function (file: fs.Dirent) {
36
+ if (file.isDirectory()) {
37
+ arrayOfDirs = getAllDirectories(dirPath + "/" + file.name, arrayOfDirs)
38
+ arrayOfDirs.push(path.join(dirPath, "/", file.name))
39
+ } else {
40
+ // ignore files
41
+ }
42
+ })
43
+ return arrayOfDirs
44
+ }
45
+
46
+ export function printIssues(result: ValidationResult, file?: string): void {
47
+ result.issues.forEach(issue => console.log((file == undefined ? "" : `File ${file}: `) + issue.errorMsg()))
48
+ }
49
+
50
+ export function issuestoString(vresult: ValidationResult, file?: string): string {
51
+ let result = "ISSUES: "
52
+ vresult.issues.forEach(issue => (result += (file === undefined ? "NOFILE" : `File ${file}: `) + issue.errorMsg() + "\n"))
53
+ return result
54
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./FileUtils.js"
2
+ export * from "./Utils.js"
@@ -0,0 +1,104 @@
1
+ import { DefinitionSchema, MAY_BE_NULL, PropertyDef, PrimitiveDef } from "./generic/index.js"
2
+ import { validateId, validateKey, validateSerializationFormatVersion, validateVersion } from "./ValidationFunctions.js"
3
+
4
+ /**
5
+ * The structure below defines the structure of a LionWeb Chunk by defining all the properties.
6
+ * It can
7
+ * - be used by the SyntaxValidator to validate an object sat runtime.
8
+ * - used to generate all the TypeScript types for a LionWebChunk.
9
+ */
10
+ export const LionWebSchema: DefinitionSchema = new DefinitionSchema([
11
+ {
12
+ name: "LionWebJsonMetaPointer",
13
+ properties: [
14
+ PropertyDef({ name: "key", type: "LionWebKey" }),
15
+ PropertyDef({ name: "version", type: "LionWebVersion" }),
16
+ PropertyDef({ name: "language", type: "LionWebKey" })
17
+ ]
18
+ },
19
+ {
20
+ name: "LionWebJsonMetaPointer",
21
+ properties: [
22
+ PropertyDef({ name: "key", type: "LionWebKey" }),
23
+ PropertyDef({ name: "version", type: "LionWebVersion" }),
24
+ PropertyDef({ name: "language", type: "LionWebKey" }),
25
+ ]
26
+ },
27
+ {
28
+ name: "ResponseMessage",
29
+ properties: [
30
+ PropertyDef({ name: "kind", type: "JSstring" }),
31
+ PropertyDef({ name: "message", type: "JSstring" }),
32
+ PropertyDef({ name: "data", type: "JSobject", mayBeNull: true, isOptional: true })
33
+ ]
34
+ },
35
+ {
36
+ name: "LionWebJsonChunk",
37
+ properties: [
38
+ PropertyDef({ name: "serializationFormatVersion", type: "LionWebSerializationFormatVersion" }),
39
+ PropertyDef({ name: "languages", type: "LionWebJsonUsedLanguage", isList: true }),
40
+ PropertyDef({ name: "nodes", type: "LionWebJsonNode", isList: true })
41
+ ]
42
+ },
43
+ {
44
+ name: "LionWebJsonUsedLanguage",
45
+ properties: [
46
+ PropertyDef({ name: "key", type: "LionWebKey" }),
47
+ PropertyDef({ name: "version", type: "LionWebVersion" })
48
+ ]
49
+ },
50
+ {
51
+ name: "LionWebJsonNode",
52
+ properties: [
53
+ PropertyDef({ name: "id", type: "LionWebId" }),
54
+ PropertyDef({ name: "classifier", type: "LionWebJsonMetaPointer" }),
55
+ PropertyDef({ name: "properties", type: "LionWebJsonProperty", isList: true }),
56
+ PropertyDef({ name: "containments", type: "LionWebJsonContainment", isList: true }),
57
+ PropertyDef({ name: "references", type: "LionWebJsonReference", isList: true }),
58
+ PropertyDef({ name: "annotations", type: "LionWebId", isList: true }),
59
+ PropertyDef({ name: "parent", type: "LionWebId", mayBeNull: MAY_BE_NULL }),
60
+ ]
61
+ },
62
+ {
63
+ name: "LionWebJsonProperty",
64
+ properties: [
65
+ PropertyDef({ name: "property", type: "LionWebJsonMetaPointer" }),
66
+ PropertyDef({ name: "value", type: "JSstring", mayBeNull: MAY_BE_NULL }),
67
+ ]
68
+ },
69
+ {
70
+ name: "LionWebJsonContainment",
71
+ properties: [
72
+ PropertyDef({ name: "containment", type: "LionWebJsonMetaPointer" }),
73
+ PropertyDef({ name: "children", type: "LionWebId", isList: true }),
74
+ ]
75
+ },
76
+ {
77
+ name: "LionWebJsonReference",
78
+ properties: [
79
+ PropertyDef({ name: "reference", type: "LionWebJsonMetaPointer"}),
80
+ PropertyDef({ name: "targets", type: "LionWebJsonReferenceTarget", isList: true}),
81
+ ]
82
+ },
83
+ {
84
+ name: "LionWebJsonReferenceTarget",
85
+ properties: [
86
+ PropertyDef({ name: "resolveInfo", type: "JSstring", mayBeNull: MAY_BE_NULL }),
87
+ PropertyDef({ name: "reference", type: "LionWebId", mayBeNull: MAY_BE_NULL }),
88
+ ]
89
+ },
90
+ /**
91
+ * Elements without properties are assumed to be JSON/JS primitive values, and tested using `typeof`
92
+ * and the (optional) validate function.
93
+ */
94
+ PrimitiveDef({ name: "LionWebId", primitiveType: "string", validate: validateId }),
95
+ PrimitiveDef({ name: "LionWebKey", primitiveType: "string", validate: validateKey }),
96
+ PrimitiveDef({ name: "LionWebVersion",primitiveType: "string", validate: validateVersion }),
97
+ PrimitiveDef({ name: "LionWebSerializationFormatVersion",primitiveType: "string", validate: validateSerializationFormatVersion }),
98
+ PrimitiveDef({ name: "JSstring", primitiveType: "string" }),
99
+ PrimitiveDef({ name: "JSobject",primitiveType: "object" }),
100
+ ])
101
+
102
+
103
+
104
+
@@ -0,0 +1,201 @@
1
+ import { isEqualMetaPointer, LionWebJsonContainment, LionWebJsonNode, LionWebJsonProperty, LionWebJsonReference } from "@lionweb/json"
2
+ import {
3
+ JsonContext,
4
+ LION_CORE_BUILTINS_INAMED_NAME,
5
+ LIONWEB_BOOLEAN_TYPE, LIONWEB_INTEGER_TYPE, LIONWEB_JSON_TYPE, LIONWEB_STRING_TYPE,
6
+ LionWebJsonChunkWrapper,
7
+ M3_Keys,
8
+ MetaPointers,
9
+ NodeUtils
10
+ } from "@lionweb/json-utils"
11
+ import {
12
+ Language_ContainmentMetaPointerNotInClass_Issue,
13
+ Language_IncorrectPropertyMetaPointer_Issue,
14
+ Language_PropertyMetaPointerNotInClass_Issue,
15
+ Language_PropertyValue_Issue,
16
+ Language_ReferenceMetaPointerNotInClass_Issue,
17
+ Language_UnknownConcept_Issue
18
+ } from "../issues/index.js"
19
+ import {
20
+ Language_IncorrectContainmentMetaPointer_Issue,
21
+ Language_IncorrectReferenceMetaPointer_Issue,
22
+ Language_UnknownContainment_Issue,
23
+ Language_UnknownProperty_Issue,
24
+ Language_UnknownReference_Issue
25
+ } from "../issues/LanguageIssues.js"
26
+ // import {
27
+ // KnownLanguages, LanguageRegistry
28
+ // } from "../languages/LanguageRegistry.js"
29
+ import { LanguageRegistry } from "../languages/index.js"
30
+ import { ValidationResult } from "./generic/ValidationResult.js"
31
+ import { validateBoolean, validateInteger, validateJSON } from "./ValidationFunctions.js"
32
+
33
+
34
+ /**
35
+ * Check against the language definition
36
+ */
37
+ export class LionWebLanguageReferenceValidator {
38
+ validationResult: ValidationResult
39
+
40
+ constructor(validationResult: ValidationResult, private registry: LanguageRegistry) {
41
+ this.validationResult = validationResult
42
+ }
43
+
44
+ // reset() {
45
+ // this.validationResult.reset();
46
+ // }
47
+
48
+ // TODO test reference and children implementation
49
+ validate(obj: LionWebJsonChunkWrapper): void {
50
+ obj.jsonChunk.nodes.forEach((node, nodeIndex) => {
51
+ const nodeContext = new JsonContext(null, ["node", nodeIndex])
52
+ const nodeConcept = this.registry.getNodeByMetaPointer(node.classifier)
53
+ if (nodeConcept === undefined && nodeContext !== undefined) {
54
+ this.validationResult.issue(new Language_UnknownConcept_Issue(nodeContext, node.classifier))
55
+ return
56
+ }
57
+ node.properties.forEach((property, propIndex) => {
58
+ this.validateProperty(node, nodeConcept, property, nodeContext.concat("properties", propIndex))
59
+ })
60
+ node.containments.forEach((containment, childIndex) => {
61
+ this.validateContainment(node, nodeConcept, containment, nodeContext.concat("containments", childIndex))
62
+ })
63
+ node.references.forEach((reference, refIndex) => {
64
+ this.validateReference(node, nodeConcept, reference, nodeContext.concat("references", refIndex))
65
+ })
66
+ })
67
+ }
68
+
69
+ private validateContainment(_node: LionWebJsonNode, nodeConcept: LionWebJsonNode | undefined, containment: LionWebJsonContainment, context: JsonContext) {
70
+ const metaConcept = this.registry.getNodeByMetaPointer(containment.containment)
71
+ if (metaConcept === null || metaConcept === undefined) {
72
+ this.validationResult.issue(new Language_UnknownContainment_Issue(context, containment.containment))
73
+ return
74
+ }
75
+ if (metaConcept.classifier.key !== M3_Keys.Containment) {
76
+ this.validationResult.issue(new Language_IncorrectContainmentMetaPointer_Issue(context, containment.containment, metaConcept.classifier.key))
77
+ }
78
+ if (nodeConcept !== undefined) {
79
+ const cons = this.registry.languages.allContainments(nodeConcept)
80
+ if (!cons.includes(metaConcept)) {
81
+ this.validationResult.issue(new Language_ContainmentMetaPointerNotInClass_Issue(context, containment.containment, nodeConcept))
82
+ return
83
+ }
84
+ }
85
+ // TODO check type of children
86
+ }
87
+
88
+ private validateReference(_node: LionWebJsonNode, nodeConcept: LionWebJsonNode | undefined, ref: LionWebJsonReference, context: JsonContext) {
89
+ const referenceDefinition = this.registry.getNodeByMetaPointer(ref.reference)
90
+ if (referenceDefinition === null || referenceDefinition === undefined) {
91
+ this.validationResult.issue(new Language_UnknownReference_Issue(context, ref.reference))
92
+ return
93
+ }
94
+ if (referenceDefinition.classifier.key !== M3_Keys.Reference) {
95
+ this.validationResult.issue(new Language_IncorrectReferenceMetaPointer_Issue(context, ref.reference, referenceDefinition.classifier.key))
96
+ }
97
+ if (nodeConcept !== undefined) {
98
+ const refs = this.registry.languages.allReferenceDefinitions(nodeConcept)
99
+ if (!refs.includes(referenceDefinition)) {
100
+ this.validationResult.issue(new Language_ReferenceMetaPointerNotInClass_Issue(context, ref.reference, nodeConcept))
101
+ return
102
+ }
103
+ }
104
+
105
+ // TODO Check type of reference (if possible)
106
+
107
+ // TODO Check for duplicate targets?
108
+ // No, already done without language check
109
+ // If so, what to check because there can be either or both a `resolveInfo` and a `reference`
110
+ }
111
+
112
+ /**
113
+ * Checks wwhether the value of `prop1` is correct in relation with its property definition in the referred language.
114
+ * @param prop
115
+ */
116
+ validateProperty(_node: LionWebJsonNode, nodeConcept: LionWebJsonNode | undefined, prop: LionWebJsonProperty, context: JsonContext): void {
117
+ if (prop.value === null) {
118
+ return
119
+ }
120
+ const propertyDefinition = this.registry.getNodeByMetaPointer(prop.property)
121
+ if ( propertyDefinition === undefined) {
122
+ this.validationResult.issue(new Language_UnknownProperty_Issue(context, prop.property))
123
+ return
124
+ }
125
+ if (propertyDefinition.classifier.key !== M3_Keys.Property) {
126
+ this.validationResult.issue(new Language_IncorrectPropertyMetaPointer_Issue(context, prop.property, propertyDefinition.classifier.key))
127
+ return
128
+ }
129
+ if (nodeConcept !== undefined) {
130
+ const props = this.registry.languages.allProperties(nodeConcept)
131
+ if (!props.includes(propertyDefinition)) {
132
+ this.validationResult.issue(new Language_PropertyMetaPointerNotInClass_Issue(context, prop.property, nodeConcept))
133
+ return
134
+ }
135
+ }
136
+
137
+ const refType = NodeUtils.findReference(propertyDefinition, MetaPointers.PropertyType)
138
+ // const refType = propertyDefinition.references.find(ref => isEqualMetaPointer(ref.reference, MetaPointers.PropertyType))
139
+ const propertyName = propertyDefinition.properties.find(p => p.property.key === LION_CORE_BUILTINS_INAMED_NAME)?.value
140
+ // console.log("Found type should be " + refType.targets[0].reference);
141
+ if (propertyName !== null && propertyName !== undefined) {
142
+ if (refType !== null && refType !== undefined) {
143
+ const typeReferenceId = refType.targets[0].reference
144
+ switch (typeReferenceId) {
145
+ case LIONWEB_BOOLEAN_TYPE:
146
+ validateBoolean(prop.value, this.validationResult, context)
147
+ break
148
+ case LIONWEB_INTEGER_TYPE:
149
+ validateInteger(prop.value, this.validationResult, context)
150
+ break
151
+ case LIONWEB_STRING_TYPE:
152
+ // Each string is correct and having another JSON type is already captured
153
+ break
154
+ case LIONWEB_JSON_TYPE:
155
+ validateJSON(prop.value, this.validationResult, context)
156
+ break
157
+ default: {
158
+ // Check for enumeration
159
+ // console.log("validateProperty: 0")
160
+ const propTypeReference = NodeUtils.findReference(propertyDefinition, MetaPointers.PropertyType)
161
+ if (propTypeReference === undefined) {
162
+ // console.log("validateProperty: 1")
163
+ break
164
+ }
165
+ const propType = this.registry.findNode(propTypeReference.targets[0].reference)
166
+ if (propType === undefined) {
167
+ // console.log("validateProperty: 1.4 for " + prop.value + " ==> " + propTypeReference.targets[0].reference)
168
+ break
169
+ }
170
+ let match = false
171
+ if (isEqualMetaPointer(propType.classifier, MetaPointers.Enumeration)) {
172
+ const literalsContainment = NodeUtils.findContainment(propType, MetaPointers.EnumerationLiterals)
173
+ if (literalsContainment === undefined) {
174
+ // console.log("validateProperty: 2")
175
+ break;
176
+ }
177
+ const literals = literalsContainment.children.map(child => this.registry.findNode(child))
178
+ match = literals.some(lit => {
179
+ if (lit === undefined) {
180
+ // console.log("validateProperty: 3")
181
+ return false
182
+ }
183
+ const litKeyProp = NodeUtils.findProperty(lit, MetaPointers.IKeyedKey)
184
+ if (litKeyProp === undefined) {
185
+ // console.log("validateProperty: 4")
186
+ return false
187
+ }
188
+ return prop.value === litKeyProp.value
189
+ })
190
+ }
191
+ if (!match) {
192
+ this.validationResult.issue(new Language_PropertyValue_Issue(context, prop.property.key, prop.value, propType.classifier.key))
193
+ }
194
+ }
195
+ }
196
+ } else {
197
+ // TODO refType not found, but
198
+ }
199
+ }
200
+ }
201
+ }
@@ -0,0 +1,79 @@
1
+ import { isEqualMetaPointer, LionWebJsonChunk, LionWebJsonNode } from "@lionweb/json"
2
+ import { isLionWebM3Language, JsonContext, LionWebJsonChunkWrapper, MetaPointers } from "@lionweb/json-utils"
3
+ import { asMinimalJsonString } from "@lionweb/ts-utils"
4
+ import { GenericIssue } from "../issues/index.js"
5
+ import { MissingM3Language_Issue } from "../issues/LanguageIssues.js"
6
+ import { isConcept, LanguageRegistry } from "../languages/index.js"
7
+ import { ValidationResult } from "./generic/ValidationResult.js"
8
+
9
+ /**
10
+ * Validates whether a chunk is a valid language definition
11
+ */
12
+ export class LionWebLanguageValidator {
13
+ validationResult: ValidationResult
14
+ chunkWrapper: LionWebJsonChunkWrapper | undefined
15
+ // availableLanguages: LionWebLanguageDefinition[] = []
16
+
17
+ constructor(validationResult: ValidationResult, private registry: LanguageRegistry) {
18
+ this.validationResult = validationResult
19
+ this.chunkWrapper = undefined
20
+ }
21
+
22
+ /**
23
+ * Check whether the metamodel is a Language.
24
+ * Assumption is that _chunk_ is already validated as a correct :LionWebJsonChunk
25
+ * @param chunk
26
+ */
27
+ validateLanguage(chunk: LionWebJsonChunk) {
28
+ this.chunkWrapper = new LionWebJsonChunkWrapper(chunk)
29
+ const usedM3Language = chunk.languages.find(lang => isLionWebM3Language(lang))
30
+ if (usedM3Language === undefined) {
31
+ this.validationResult.issue(new MissingM3Language_Issue(new JsonContext(null, ["languages"])))
32
+ }
33
+
34
+ const languageNodes = this.chunkWrapper.findNodesOfClassifier(MetaPointers.Language)
35
+ if (languageNodes.length !== 1) {
36
+ // TODO Better error handling.
37
+ console.error("Error: xpected exactly one Language node, found " + languageNodes.length + " => " + asMinimalJsonString(languageNodes))
38
+ }
39
+ chunk.nodes.forEach((node, index) => {
40
+ if (!isConcept(node)) {
41
+ this.validationResult.issue(new GenericIssue(new JsonContext(null, ["nodes", index]), `node ${node.id} is not a concept`))
42
+ } else {
43
+ this.validateConcept(node)
44
+ }
45
+ })
46
+ }
47
+
48
+ validateConcept(node: LionWebJsonNode): void {
49
+ node.properties.forEach(prop => {
50
+ const properties = this.chunkWrapper?.findNodesOfClassifier(MetaPointers.Property)
51
+ const matchedProperty = properties?.find(p => isEqualMetaPointer(p.classifier, prop.property))
52
+ // DUMMY
53
+ return matchedProperty !== undefined
54
+ })
55
+ }
56
+
57
+ validateNode(node: LionWebJsonNode): void {
58
+ const classifier = this.registry.getNodeByMetaPointer(node.classifier)
59
+ if (classifier === undefined) {
60
+ this.validationResult.issue(
61
+ new GenericIssue(new JsonContext(null, ["nodes"]), `Classifier ${node.classifier.key} not found for node ${node.id}`)
62
+ )
63
+ return
64
+ }
65
+ this.validateProperties(node, classifier)
66
+ }
67
+
68
+ validateProperties(node: LionWebJsonNode, classifier: LionWebJsonNode): void {
69
+ node.properties.forEach(actualProp => {
70
+ const propClassifier = this.registry.getNodeByMetaPointer(actualProp.property)
71
+ if (propClassifier === undefined) {
72
+ this.validationResult.issue(
73
+ new GenericIssue(new JsonContext(null, ["nodes", "properties"]),
74
+ `Property ${actualProp.property.key} not found for classifier ${classifier.id}`)
75
+ )
76
+ }
77
+ })
78
+ }
79
+ }