@oml/language 0.7.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.
Files changed (91) hide show
  1. package/README.md +44 -0
  2. package/out/oml/generated/ast.d.ts +2109 -0
  3. package/out/oml/generated/ast.js +1807 -0
  4. package/out/oml/generated/ast.js.map +1 -0
  5. package/out/oml/generated/grammar.d.ts +6 -0
  6. package/out/oml/generated/grammar.js +6885 -0
  7. package/out/oml/generated/grammar.js.map +1 -0
  8. package/out/oml/generated/module.d.ts +13 -0
  9. package/out/oml/generated/module.js +21 -0
  10. package/out/oml/generated/module.js.map +1 -0
  11. package/out/oml/index.d.ts +17 -0
  12. package/out/oml/index.js +19 -0
  13. package/out/oml/index.js.map +1 -0
  14. package/out/oml/oml-candidates.d.ts +27 -0
  15. package/out/oml/oml-candidates.js +146 -0
  16. package/out/oml/oml-candidates.js.map +1 -0
  17. package/out/oml/oml-code-actions.d.ts +6 -0
  18. package/out/oml/oml-code-actions.js +79 -0
  19. package/out/oml/oml-code-actions.js.map +1 -0
  20. package/out/oml/oml-completion.d.ts +50 -0
  21. package/out/oml/oml-completion.js +188 -0
  22. package/out/oml/oml-completion.js.map +1 -0
  23. package/out/oml/oml-converter.d.ts +10 -0
  24. package/out/oml/oml-converter.js +62 -0
  25. package/out/oml/oml-converter.js.map +1 -0
  26. package/out/oml/oml-document-validator.d.ts +10 -0
  27. package/out/oml/oml-document-validator.js +31 -0
  28. package/out/oml/oml-document-validator.js.map +1 -0
  29. package/out/oml/oml-document.d.ts +9 -0
  30. package/out/oml/oml-document.js +18 -0
  31. package/out/oml/oml-document.js.map +1 -0
  32. package/out/oml/oml-edit.d.ts +72 -0
  33. package/out/oml/oml-edit.js +1155 -0
  34. package/out/oml/oml-edit.js.map +1 -0
  35. package/out/oml/oml-formatter.d.ts +22 -0
  36. package/out/oml/oml-formatter.js +357 -0
  37. package/out/oml/oml-formatter.js.map +1 -0
  38. package/out/oml/oml-hover.d.ts +13 -0
  39. package/out/oml/oml-hover.js +71 -0
  40. package/out/oml/oml-hover.js.map +1 -0
  41. package/out/oml/oml-index-manager.d.ts +10 -0
  42. package/out/oml/oml-index-manager.js +48 -0
  43. package/out/oml/oml-index-manager.js.map +1 -0
  44. package/out/oml/oml-index.d.ts +20 -0
  45. package/out/oml/oml-index.js +133 -0
  46. package/out/oml/oml-index.js.map +1 -0
  47. package/out/oml/oml-module.d.ts +42 -0
  48. package/out/oml/oml-module.js +76 -0
  49. package/out/oml/oml-module.js.map +1 -0
  50. package/out/oml/oml-rename.d.ts +14 -0
  51. package/out/oml/oml-rename.js +114 -0
  52. package/out/oml/oml-rename.js.map +1 -0
  53. package/out/oml/oml-scope.d.ts +30 -0
  54. package/out/oml/oml-scope.js +225 -0
  55. package/out/oml/oml-scope.js.map +1 -0
  56. package/out/oml/oml-serializer.d.ts +2 -0
  57. package/out/oml/oml-serializer.js +883 -0
  58. package/out/oml/oml-serializer.js.map +1 -0
  59. package/out/oml/oml-utils.d.ts +53 -0
  60. package/out/oml/oml-utils.js +241 -0
  61. package/out/oml/oml-utils.js.map +1 -0
  62. package/out/oml/oml-validator.d.ts +49 -0
  63. package/out/oml/oml-validator.js +668 -0
  64. package/out/oml/oml-validator.js.map +1 -0
  65. package/out/oml/oml-workspace.d.ts +23 -0
  66. package/out/oml/oml-workspace.js +68 -0
  67. package/out/oml/oml-workspace.js.map +1 -0
  68. package/package.json +50 -0
  69. package/src/oml/generated/ast.ts +2641 -0
  70. package/src/oml/generated/grammar.ts +6887 -0
  71. package/src/oml/generated/module.ts +25 -0
  72. package/src/oml/index.ts +19 -0
  73. package/src/oml/oml-candidates.ts +176 -0
  74. package/src/oml/oml-code-actions.ts +120 -0
  75. package/src/oml/oml-completion.ts +222 -0
  76. package/src/oml/oml-converter.ts +66 -0
  77. package/src/oml/oml-document-validator.ts +39 -0
  78. package/src/oml/oml-document.ts +24 -0
  79. package/src/oml/oml-edit.ts +1292 -0
  80. package/src/oml/oml-formatter.ts +390 -0
  81. package/src/oml/oml-hover.ts +93 -0
  82. package/src/oml/oml-index-manager.ts +56 -0
  83. package/src/oml/oml-index.ts +145 -0
  84. package/src/oml/oml-module.ts +105 -0
  85. package/src/oml/oml-rename.ts +140 -0
  86. package/src/oml/oml-scope.ts +279 -0
  87. package/src/oml/oml-serializer.ts +1080 -0
  88. package/src/oml/oml-utils.ts +294 -0
  89. package/src/oml/oml-validator.ts +725 -0
  90. package/src/oml/oml-workspace.ts +81 -0
  91. package/src/oml/oml.langium +594 -0
@@ -0,0 +1,668 @@
1
+ // Copyright (c) 2026 Modelware. All rights reserved.
2
+ import { AstUtils } from 'langium';
3
+ import { getOntologyModelIndex } from './oml-index.js';
4
+ import { collectOntologyMembers, findOwningOntology, findOwningOntologyNode, getMemberName, getNamedElementName, } from './oml-utils.js';
5
+ /**
6
+ * Register custom validation checks.
7
+ */
8
+ export function registerValidationChecks(services) {
9
+ const registry = services.validation.ValidationRegistry;
10
+ const validator = services.validation.OmlValidator;
11
+ const checks = {
12
+ // Abstact types
13
+ Element: [validator.checkDeprecatedReferences],
14
+ IdentifiedElement: [],
15
+ Member: [validator.checkDuplicateMemberNames],
16
+ VocabularyMember: [],
17
+ DescriptionMember: [],
18
+ Statement: [],
19
+ VocabularyStatement: [],
20
+ DescriptionStatement: [],
21
+ Term: [],
22
+ SpecializableTerm: [],
23
+ // Annotation types
24
+ Annotation: [],
25
+ // Import types
26
+ Import: [
27
+ validator.checkInvalidImport,
28
+ validator.checkUnusedImport,
29
+ validator.checkDuplicateImport,
30
+ validator.checkDuplicateImportPrefix,
31
+ ],
32
+ // Axiom types
33
+ Axiom: [],
34
+ KeyAxiom: [],
35
+ SpecializationAxiom: [validator.checkInvalidSpecialization],
36
+ EquivalenceAxiom: [],
37
+ EntityEquivalenceAxiom: [validator.checkInvalidEntityEquivalence],
38
+ ScalarEquivalenceAxiom: [validator.checkInvalidScalarEquivalence],
39
+ PropertyEquivalenceAxiom: [validator.checkInvalidPropertyEquivalence],
40
+ InstanceEnumerationAxiom: [],
41
+ LiteralEnumerationAxiom: [],
42
+ PropertyRestrictionAxiom: [],
43
+ PropertyRangeRestrictionAxiom: [],
44
+ PropertyCardinalityRestrictionAxiom: [],
45
+ PropertyValueRestrictionAxiom: [],
46
+ PropertySelfRestrictionAxiom: [],
47
+ // Instance types
48
+ Instance: [],
49
+ NamedInstance: [],
50
+ AnonymousInstance: [],
51
+ ConceptInstance: [],
52
+ RelationInstance: [validator.checkInvalidRelationInstance],
53
+ AnonymousConceptInstance: [],
54
+ AnonymousRelationInstance: [],
55
+ // Assertion types
56
+ Assertion: [],
57
+ TypeAssertion: [validator.checkInvalidInstanceType],
58
+ PropertyValueAssertion: [],
59
+ // Literal types
60
+ Literal: [],
61
+ IntegerLiteral: [],
62
+ DecimalLiteral: [],
63
+ DoubleLiteral: [],
64
+ BooleanLiteral: [],
65
+ QuotedLiteral: [validator.checkQuotedLiteralNonStandardType],
66
+ // Rule types
67
+ Rule: [validator.checkInvalidRule],
68
+ Argument: [],
69
+ BuiltIn: [],
70
+ Predicate: [],
71
+ UnaryPredicate: [],
72
+ BinaryPredicate: [],
73
+ BuiltInPredicate: [],
74
+ TypePredicate: [],
75
+ RelationEntityPredicate: [],
76
+ PropertyPredicate: [],
77
+ SameAsPredicate: [],
78
+ DifferentFromPredicate: [],
79
+ //Ontology types
80
+ Ontology: [validator.checkOntologyNamespace],
81
+ VocabularyBox: [],
82
+ DescriptionBox: [],
83
+ Vocabulary: [],
84
+ VocabularyBundle: [],
85
+ Description: [],
86
+ DescriptionBundle: [],
87
+ // Property types
88
+ Property: [],
89
+ RelationBase: [],
90
+ SpecializableProperty: [],
91
+ AnnotationProperty: [],
92
+ SemanticProperty: [],
93
+ ScalarProperty: [],
94
+ Relation: [],
95
+ ForwardRelation: [validator.checkInvalidForwardRelation],
96
+ ReverseRelation: [validator.checkInvalidReverseRelation],
97
+ UnreifiedRelation: [],
98
+ // Type types
99
+ Type: [],
100
+ Scalar: [validator.checkInvalidScalarSpecialization],
101
+ Entity: [],
102
+ Aspect: [],
103
+ Concept: [],
104
+ RelationEntity: [validator.checkInvalidRelationEntity],
105
+ };
106
+ registry.register(checks, validator);
107
+ }
108
+ /**
109
+ * Implementation of custom validations.
110
+ */
111
+ export class OmlValidator {
112
+ constructor(services) {
113
+ this.services = services;
114
+ this.ontologyContextCache = new Map();
115
+ this.deprecatedMemberCache = new WeakMap();
116
+ this.annotationPropertyIriCache = new WeakMap();
117
+ }
118
+ checkInvalidImport(ownedImport, accept) {
119
+ const ontology = ownedImport.$container;
120
+ // If unresolved, let linker/scope handle
121
+ if (!ownedImport.imported?.ref)
122
+ return;
123
+ const imported = ownedImport.imported.ref;
124
+ const kind = ownedImport.kind;
125
+ const type = ontology?.$type;
126
+ const importedType = imported?.$type;
127
+ const err = (msg) => accept('error', msg, { node: ownedImport });
128
+ if (kind === 'extends') {
129
+ if (type !== importedType) {
130
+ return err(`${type}s can extend other ${type}s (extending ${importedType})`);
131
+ }
132
+ }
133
+ else if (kind === 'includes') {
134
+ if (type === 'VocabularyBundle') {
135
+ if (importedType !== 'Vocabulary') {
136
+ return err(`Vocabulary Bundles can include Vocabularies (including ${importedType})`);
137
+ }
138
+ }
139
+ else if (type === 'DescriptionBundle') {
140
+ if (importedType !== 'Description') {
141
+ return err(`Description bundles can include Descriptions (including ${importedType})`);
142
+ }
143
+ }
144
+ else {
145
+ return err('Only Vocabulary Bundles and Description Bundles can include other ontologies');
146
+ }
147
+ }
148
+ else if (kind === 'uses') {
149
+ if (type === 'Vocabulary') {
150
+ if (importedType !== 'Description') {
151
+ return err(`Vocabularies use Descriptions (using ${importedType})`);
152
+ }
153
+ }
154
+ else if (type === 'DescriptionBundle') {
155
+ if (importedType !== 'Vocabulary' && importedType !== 'VocabularyBundle') {
156
+ return err(`Description bundles can use Vocabularies or Vocabulary Bundles (using ${importedType})`);
157
+ }
158
+ }
159
+ else if (type === 'Description') {
160
+ if (importedType !== 'Vocabulary') {
161
+ return err(`Descriptions can use Vocabularies (using ${importedType})`);
162
+ }
163
+ }
164
+ else {
165
+ return err('Only Vocabularies, Descriptions, and Description Bundles can use other ontologies');
166
+ }
167
+ }
168
+ else {
169
+ return err(`Unknown import kind: ${kind}`);
170
+ }
171
+ }
172
+ checkUnusedImport(ownedImport, accept) {
173
+ let ontology = ownedImport.$container;
174
+ const prefix = ownedImport.prefix;
175
+ if (!ontology || !prefix)
176
+ return;
177
+ const context = this.getOntologyValidationContext(ontology);
178
+ if (!context?.usedPrefixes.has(prefix)) {
179
+ accept('warning', `Could not find a reference to prefix '${ownedImport.prefix}'`, {
180
+ node: ownedImport
181
+ });
182
+ }
183
+ }
184
+ checkDuplicateImport(ownedImport, accept) {
185
+ const ontology = ownedImport.$container;
186
+ const context = this.getOntologyValidationContext(ontology);
187
+ if (!context)
188
+ return;
189
+ const key = this.getImportKey(ownedImport);
190
+ if (!key)
191
+ return;
192
+ const hasDuplicate = (context.importKeyCounts.get(key) ?? 0) > 1;
193
+ if (hasDuplicate) {
194
+ const target = this.getImportTarget(ownedImport) ?? 'ontology';
195
+ const prefix = ownedImport.prefix ? ` as ${ownedImport.prefix}` : '';
196
+ accept('warning', `Duplicate import: ${ownedImport.kind} ${target}${prefix}`, {
197
+ node: ownedImport
198
+ });
199
+ }
200
+ }
201
+ checkDuplicateImportPrefix(ownedImport, accept) {
202
+ const ontology = ownedImport.$container;
203
+ const context = this.getOntologyValidationContext(ontology);
204
+ if (!context)
205
+ return;
206
+ const prefix = ownedImport.prefix?.trim();
207
+ if (!prefix)
208
+ return;
209
+ if (context.ontologyPrefix && context.ontologyPrefix === prefix) {
210
+ accept('error', `Import prefix '${prefix}' duplicates the owning ontology prefix`, {
211
+ node: ownedImport,
212
+ property: 'prefix'
213
+ });
214
+ return;
215
+ }
216
+ const hasDuplicateImportPrefix = (context.importPrefixCounts.get(prefix) ?? 0) > 1;
217
+ if (hasDuplicateImportPrefix) {
218
+ accept('error', `Duplicate import prefix '${prefix}'`, {
219
+ node: ownedImport,
220
+ property: 'prefix'
221
+ });
222
+ }
223
+ }
224
+ checkDeprecatedReferences(element, accept) {
225
+ for (const refInfo of AstUtils.streamReferences(element)) {
226
+ const target = refInfo.reference?.ref;
227
+ if (!target || !this.isDeprecatedMember(target)) {
228
+ continue;
229
+ }
230
+ accept('warning', `Reference to deprecated member ${this.getAbbreviatedIri(target)}`, {
231
+ node: refInfo.container,
232
+ property: refInfo.property,
233
+ index: refInfo.index
234
+ });
235
+ }
236
+ }
237
+ checkOntologyNamespace(ontology, accept) {
238
+ const rawNamespace = typeof ontology?.namespace === 'string' ? ontology.namespace.trim() : '';
239
+ if (!rawNamespace)
240
+ return;
241
+ const namespace = rawNamespace.replace(/^<|>$/g, '');
242
+ const iri = namespace.replace(/[#/]?$/, '');
243
+ const docUri = ontology?.$document?.uri?.toString?.() ?? '';
244
+ const duplicateModelUris = getOntologyModelIndex(this.services.shared).getDuplicateWorkspaceModelUris(iri);
245
+ const conflictingModelUris = duplicateModelUris.filter((modelUri) => modelUri !== docUri);
246
+ if (conflictingModelUris.length > 0) {
247
+ accept('error', `Ontology IRI '${iri}' is also declared by '${conflictingModelUris[0]}'`, {
248
+ node: ontology,
249
+ property: 'namespace'
250
+ });
251
+ }
252
+ let nsUrl;
253
+ try {
254
+ nsUrl = new URL(iri);
255
+ }
256
+ catch {
257
+ return;
258
+ }
259
+ const expectedHost = nsUrl.hostname;
260
+ const expectedPath = nsUrl.pathname.replace(/\/+$/, '');
261
+ if (!expectedHost || !expectedPath) {
262
+ return;
263
+ }
264
+ const expectedSuffix = `/${expectedHost}${expectedPath}.oml`;
265
+ let documentPath = '';
266
+ try {
267
+ documentPath = decodeURIComponent(new URL(docUri).pathname).replace(/\\/g, '/');
268
+ }
269
+ catch {
270
+ return;
271
+ }
272
+ if (!documentPath.toLowerCase().endsWith('.oml')) {
273
+ return;
274
+ }
275
+ if (!documentPath.endsWith(expectedSuffix)) {
276
+ accept('error', `Ontology namespace '${namespace}' does not match document path '${documentPath}'`, {
277
+ node: ontology,
278
+ property: 'namespace'
279
+ });
280
+ }
281
+ }
282
+ checkQuotedLiteralNonStandardType(literal, accept) {
283
+ const scalar = literal.type?.ref;
284
+ if (!scalar) {
285
+ return;
286
+ }
287
+ const ontology = findOwningOntologyNode(scalar);
288
+ const namespace = typeof ontology?.namespace === 'string' ? ontology.namespace : '';
289
+ if (!namespace || !OmlValidator.StandardScalarNamespaces.has(namespace)) {
290
+ accept('error', `Quoted Literal "${literal.value}" is not typed by a standard scalar`, {
291
+ node: literal,
292
+ property: 'type'
293
+ });
294
+ }
295
+ }
296
+ checkInvalidEntityEquivalence(axiom, accept) {
297
+ const container = axiom.$container;
298
+ const subType = container?.$type;
299
+ if (subType !== 'Aspect' && subType !== 'Concept' && subType !== 'RelationEntity') {
300
+ return;
301
+ }
302
+ for (const superTerm of axiom.superTerms ?? []) {
303
+ const superType = superTerm?.ref?.$type;
304
+ if (!superType || superType === subType) {
305
+ continue;
306
+ }
307
+ accept('error', `An ${subType} can only be equivalent to ${subType} (not ${superType})`, {
308
+ node: axiom,
309
+ property: 'superTerms'
310
+ });
311
+ return;
312
+ }
313
+ }
314
+ checkInvalidPropertyEquivalence(axiom, accept) {
315
+ const subKind = this.getPropertyKind(axiom.$container);
316
+ if (!subKind) {
317
+ return;
318
+ }
319
+ for (const superTerm of axiom.superTerms ?? []) {
320
+ const superKind = this.getPropertyKind(superTerm?.ref);
321
+ if (!superKind || superKind === subKind) {
322
+ continue;
323
+ }
324
+ accept('error', `A ${subKind} can only be equivalent to ${subKind} (not ${superKind})`, {
325
+ node: axiom,
326
+ property: 'superTerms'
327
+ });
328
+ return;
329
+ }
330
+ }
331
+ checkInvalidScalarEquivalence(axiom, accept) {
332
+ const hasFacets = (axiom.length?.length ?? 0) > 0
333
+ || (axiom.minLength?.length ?? 0) > 0
334
+ || (axiom.maxLength?.length ?? 0) > 0
335
+ || (axiom.pattern?.length ?? 0) > 0
336
+ || (axiom.language?.length ?? 0) > 0
337
+ || (axiom.minInclusive?.length ?? 0) > 0
338
+ || (axiom.minExclusive?.length ?? 0) > 0
339
+ || (axiom.maxInclusive?.length ?? 0) > 0
340
+ || (axiom.maxExclusive?.length ?? 0) > 0;
341
+ if (!hasFacets) {
342
+ return;
343
+ }
344
+ const resolvedSupers = (axiom.superTerms ?? [])
345
+ .map((superTerm) => superTerm?.ref)
346
+ .filter(Boolean);
347
+ if (resolvedSupers.length === 0) {
348
+ return;
349
+ }
350
+ const hasStandardSuper = resolvedSupers.some((scalar) => this.isStandardScalar(scalar));
351
+ if (!hasStandardSuper) {
352
+ accept('error', 'Facets can only restrict standard super scalars', {
353
+ node: axiom,
354
+ property: 'superTerms'
355
+ });
356
+ }
357
+ }
358
+ checkInvalidScalarSpecialization(scalar, accept) {
359
+ const isStandardType = this.isStandardScalar(scalar);
360
+ const hasSpecializations = (scalar.ownedSpecializations?.length ?? 0) > 0;
361
+ if (!isStandardType && hasSpecializations) {
362
+ accept('error', `Non-standard Scalar ${this.getAbbreviatedIri(scalar)} cannot have specializations`, {
363
+ node: scalar,
364
+ property: 'ownedSpecializations'
365
+ });
366
+ return;
367
+ }
368
+ const superScalars = (scalar.ownedSpecializations ?? [])
369
+ .map((specialization) => specialization.superTerm?.ref)
370
+ .filter((term) => Boolean(term) && term.$type === 'Scalar');
371
+ const hasAllStandardSupers = superScalars.every((superScalar) => this.isStandardScalar(superScalar));
372
+ if (!hasAllStandardSupers) {
373
+ accept('error', `Standard scalar ${this.getAbbreviatedIri(scalar)} can only specialize standard scalars`, {
374
+ node: scalar,
375
+ property: 'ownedSpecializations'
376
+ });
377
+ }
378
+ }
379
+ checkInvalidInstanceType(assertion, accept) {
380
+ const instance = assertion.$container;
381
+ const type = assertion.type?.ref;
382
+ if (!type || !instance) {
383
+ return;
384
+ }
385
+ const instanceType = instance.$type;
386
+ const typeType = type.$type;
387
+ const invalidConceptInstanceType = instanceType === 'ConceptInstance' && typeType !== 'Concept' && typeType !== 'Aspect';
388
+ const invalidRelationInstanceType = instanceType === 'RelationInstance' && typeType !== 'RelationEntity' && typeType !== 'Aspect';
389
+ if (invalidConceptInstanceType || invalidRelationInstanceType) {
390
+ accept('error', `Type ${this.getAbbreviatedIri(type)} cannot be a type for instance ${this.getAbbreviatedIri(instance)}`, {
391
+ node: assertion,
392
+ property: 'type'
393
+ });
394
+ }
395
+ }
396
+ checkInvalidReverseRelation(relation, accept) {
397
+ const relationBase = relation.$container;
398
+ if (relationBase?.ref) {
399
+ accept('error', `Cannot name a reverse relation on a ref to ${this.getAbbreviatedIri(relationBase.ref)}`, {
400
+ node: relation,
401
+ property: 'name'
402
+ });
403
+ }
404
+ }
405
+ checkInvalidForwardRelation(relation, accept) {
406
+ const relationEntity = relation.$container;
407
+ if (relationEntity?.ref) {
408
+ accept('error', `Cannot name a forward relation on a ref to ${this.getAbbreviatedIri(relationEntity.ref)}`, {
409
+ node: relation,
410
+ property: 'name'
411
+ });
412
+ }
413
+ }
414
+ checkInvalidRelationEntity(entity, accept) {
415
+ if (!entity.ref && !entity.forwardRelation) {
416
+ accept('error', `Must name a forward relation on ${this.getAbbreviatedIri(entity)}`, {
417
+ node: entity,
418
+ property: 'forwardRelation'
419
+ });
420
+ }
421
+ }
422
+ checkInvalidRule(rule, accept) {
423
+ const hasAntecedent = (rule.antecedent?.length ?? 0) > 0;
424
+ const hasConsequent = (rule.consequent?.length ?? 0) > 0;
425
+ if (rule.name) {
426
+ if (!hasAntecedent) {
427
+ accept('error', `Rule ${this.getAbbreviatedIri(rule)} needs to specify some predicates as antecedent`, {
428
+ node: rule,
429
+ property: 'name'
430
+ });
431
+ return;
432
+ }
433
+ if (!hasConsequent) {
434
+ accept('error', `Rule ${this.getAbbreviatedIri(rule)} needs to specify some predicates as consequent`, {
435
+ node: rule,
436
+ property: 'name'
437
+ });
438
+ }
439
+ return;
440
+ }
441
+ if (hasAntecedent) {
442
+ accept('error', `Rule ${this.getAbbreviatedIri(rule)} cannot respecify a predeicate as antecedent`, {
443
+ node: rule,
444
+ property: 'antecedent'
445
+ });
446
+ }
447
+ if (hasConsequent) {
448
+ accept('error', `Rule ${this.getAbbreviatedIri(rule)} cannot respecify a predicate as consequent`, {
449
+ node: rule,
450
+ property: 'consequent'
451
+ });
452
+ }
453
+ }
454
+ checkInvalidRelationInstance(instance, accept) {
455
+ if (!instance.name) {
456
+ return;
457
+ }
458
+ if ((instance.sources?.length ?? 0) === 0) {
459
+ accept('error', `Relation instance ${this.getAbbreviatedIri(instance)} needs to specify at least one source`, {
460
+ node: instance,
461
+ property: 'name'
462
+ });
463
+ return;
464
+ }
465
+ if ((instance.targets?.length ?? 0) === 0) {
466
+ accept('error', `Relation instance ${this.getAbbreviatedIri(instance)} needs to specify at least one target`, {
467
+ node: instance,
468
+ property: 'name'
469
+ });
470
+ }
471
+ }
472
+ getPropertyKind(property) {
473
+ const type = property?.$type;
474
+ if (type === 'AnnotationProperty') {
475
+ return 'annotation property';
476
+ }
477
+ if (type === 'ScalarProperty') {
478
+ return 'scalar property';
479
+ }
480
+ if (type === 'UnreifiedRelation' || type === 'ForwardRelation' || type === 'ReverseRelation') {
481
+ return 'relation';
482
+ }
483
+ return undefined;
484
+ }
485
+ isStandardScalar(scalar) {
486
+ const ontology = findOwningOntologyNode(scalar);
487
+ const namespace = typeof ontology?.namespace === 'string' ? ontology.namespace : '';
488
+ return Boolean(namespace) && OmlValidator.StandardScalarNamespaces.has(namespace);
489
+ }
490
+ isDeprecatedMember(member) {
491
+ if (!member || typeof member !== 'object') {
492
+ return false;
493
+ }
494
+ const cached = this.deprecatedMemberCache.get(member);
495
+ if (cached !== undefined) {
496
+ return cached;
497
+ }
498
+ const annotations = Array.isArray(member.ownedAnnotations) ? member.ownedAnnotations : [];
499
+ const deprecated = annotations.some((annotation) => {
500
+ const property = annotation?.property?.ref;
501
+ const iri = this.getAnnotationPropertyIri(property);
502
+ return iri === OmlValidator.DeprecatedAnnotationPropertyIri;
503
+ });
504
+ this.deprecatedMemberCache.set(member, deprecated);
505
+ return deprecated;
506
+ }
507
+ getAnnotationPropertyIri(property) {
508
+ if (!property || typeof property !== 'object') {
509
+ return undefined;
510
+ }
511
+ const cached = this.annotationPropertyIriCache.get(property);
512
+ if (cached !== undefined) {
513
+ return cached;
514
+ }
515
+ const name = typeof property.name === 'string' ? property.name.trim() : '';
516
+ const ontology = findOwningOntologyNode(property);
517
+ const namespace = typeof ontology?.namespace === 'string' ? ontology.namespace.replace(/^<|>$/g, '') : '';
518
+ let iri;
519
+ if (namespace && name) {
520
+ const separator = namespace.endsWith('#') || namespace.endsWith('/') ? '' : '#';
521
+ iri = `${namespace}${separator}${name}`;
522
+ }
523
+ this.annotationPropertyIriCache.set(property, iri);
524
+ return iri;
525
+ }
526
+ getAbbreviatedIri(node) {
527
+ const ontology = findOwningOntologyNode(node);
528
+ const prefix = typeof ontology?.prefix === 'string' ? ontology.prefix : '';
529
+ const name = typeof node?.name === 'string' ? node.name : '';
530
+ if (prefix && name) {
531
+ return `${prefix}:${name}`;
532
+ }
533
+ return name || '<unknown>';
534
+ }
535
+ checkInvalidSpecialization(specializationAxiom, accept) {
536
+ const subTerm = specializationAxiom?.$container;
537
+ const superTerm = specializationAxiom?.superTerm?.ref;
538
+ if (!subTerm || !superTerm)
539
+ return;
540
+ const subType = subTerm.$type;
541
+ const superType = superTerm.$type;
542
+ const err = (msg) => accept('error', msg, { node: specializationAxiom });
543
+ const validSpecializations = {
544
+ 'Concept': ['Concept', 'Aspect'],
545
+ 'Aspect': ['Aspect'],
546
+ 'RelationEntity': ['RelationEntity', 'Aspect'],
547
+ 'AnnotationProperty': ['AnnotationProperty'],
548
+ 'Scalar': ['Scalar'],
549
+ 'ScalarProperty': ['ScalarProperty'],
550
+ 'UnreifiedRelation': ['UnreifiedRelation', 'ForwardRelation', 'ReverseRelation'],
551
+ };
552
+ const allowedSupers = validSpecializations[subType];
553
+ if (!allowedSupers) {
554
+ return err(`Invalid subTerm type '${subType}' in SpecializationAxiom`);
555
+ }
556
+ if (!allowedSupers.includes(superType)) {
557
+ return err(`A ${subType} can only specialize ${allowedSupers.join(' or ')} (not ${superType})`);
558
+ }
559
+ }
560
+ checkDuplicateMemberNames(member, accept) {
561
+ const memberName = getMemberName(member);
562
+ if (!memberName) {
563
+ return;
564
+ }
565
+ const ontology = findOwningOntology(member);
566
+ if (!ontology) {
567
+ return;
568
+ }
569
+ const context = this.getOntologyValidationContext(ontology);
570
+ const hasDuplicate = (context?.memberNameCounts.get(memberName) ?? 0) > 1;
571
+ if (!hasDuplicate) {
572
+ return;
573
+ }
574
+ accept('error', `An element named '${memberName}' already exists in this ontology`, {
575
+ node: member,
576
+ property: 'name',
577
+ });
578
+ }
579
+ getImportTarget(ownedImport) {
580
+ return ownedImport.imported?.$refText;
581
+ }
582
+ getImportKey(ownedImport) {
583
+ const target = this.getImportTarget(ownedImport);
584
+ if (!target)
585
+ return undefined;
586
+ const prefix = ownedImport.prefix ?? '';
587
+ const kind = ownedImport.kind ?? '';
588
+ return `${kind}|${target}|${prefix}`;
589
+ }
590
+ getOntologyValidationContext(ontology) {
591
+ const uri = ontology?.$document?.uri?.toString?.();
592
+ if (!uri) {
593
+ return undefined;
594
+ }
595
+ const version = ontology?.$document?.textDocument?.version ?? 0;
596
+ const cached = this.ontologyContextCache.get(uri);
597
+ if (cached && cached.version === version) {
598
+ return cached;
599
+ }
600
+ const text = ontology?.$document?.textDocument?.getText?.() ?? '';
601
+ const usedPrefixes = this.collectUsedPrefixes(text);
602
+ const memberNameCounts = new Map();
603
+ const importKeyCounts = new Map();
604
+ const importPrefixCounts = new Map();
605
+ for (const candidate of collectOntologyMembers(ontology)) {
606
+ const name = getNamedElementName(candidate);
607
+ if (name) {
608
+ memberNameCounts.set(name, (memberNameCounts.get(name) ?? 0) + 1);
609
+ }
610
+ }
611
+ const imports = ontology?.ownedImports ?? [];
612
+ for (const ownedImport of imports) {
613
+ const key = this.getImportKey(ownedImport);
614
+ if (key) {
615
+ importKeyCounts.set(key, (importKeyCounts.get(key) ?? 0) + 1);
616
+ }
617
+ const prefix = typeof ownedImport?.prefix === 'string' ? ownedImport.prefix.trim() : '';
618
+ if (prefix) {
619
+ importPrefixCounts.set(prefix, (importPrefixCounts.get(prefix) ?? 0) + 1);
620
+ }
621
+ }
622
+ const context = {
623
+ version,
624
+ usedPrefixes,
625
+ memberNameCounts,
626
+ importKeyCounts,
627
+ importPrefixCounts,
628
+ ontologyPrefix: typeof ontology?.prefix === 'string' ? ontology.prefix.trim() : ''
629
+ };
630
+ this.ontologyContextCache.set(uri, context);
631
+ this.pruneOntologyContextCache();
632
+ return context;
633
+ }
634
+ collectUsedPrefixes(text) {
635
+ // Mask quoted literals and comments so prefix scanning only sees code tokens.
636
+ const stripped = text.replace(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\/\*[\s\S]*?\*\/|\/\/[^\n\r]*/g, ' ');
637
+ const usedPrefixes = new Set();
638
+ const pattern = /\b([A-Za-z_][A-Za-z0-9_-]*):\S/g;
639
+ let match;
640
+ while ((match = pattern.exec(stripped)) !== null) {
641
+ usedPrefixes.add(match[1]);
642
+ }
643
+ return usedPrefixes;
644
+ }
645
+ pruneOntologyContextCache() {
646
+ if (this.ontologyContextCache.size <= OmlValidator.MaxOntologyContexts) {
647
+ return;
648
+ }
649
+ const overflow = this.ontologyContextCache.size - OmlValidator.MaxOntologyContexts;
650
+ const keys = this.ontologyContextCache.keys();
651
+ for (let i = 0; i < overflow; i += 1) {
652
+ const next = keys.next();
653
+ if (next.done) {
654
+ break;
655
+ }
656
+ this.ontologyContextCache.delete(next.value);
657
+ }
658
+ }
659
+ }
660
+ OmlValidator.MaxOntologyContexts = 512;
661
+ OmlValidator.StandardScalarNamespaces = new Set([
662
+ 'http://www.w3.org/2001/XMLSchema#',
663
+ 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
664
+ 'http://www.w3.org/2000/01/rdf-schema#',
665
+ 'http://www.w3.org/2002/07/owl#'
666
+ ]);
667
+ OmlValidator.DeprecatedAnnotationPropertyIri = 'http://www.w3.org/2002/07/owl#deprecated';
668
+ //# sourceMappingURL=oml-validator.js.map