@itwin/ecschema-metadata 5.15.0-dev.7 → 5.15.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.
@@ -137,8 +137,8 @@ export class SchemaView {
137
137
  throw new Error("SchemaView is not mergeable: create it via SchemaView.createMergeable to merge fragments.");
138
138
  this._mergeContext.mergeBlob(blob);
139
139
  // A new fragment can add subclasses to an already-present base class, so the derived-class map
140
- // must be rebuilt. transitiveBaseCache stays valid: a class's ancestors are always merged
141
- // before it (dependency order), so an append never adds an ancestor to an already-cached class.
140
+ // must be rebuilt. transitiveBaseCache and PropertyRef.overriddenProperty stay valid: a class's ancestors
141
+ // are always merged before it (dependency order), so an append never adds an ancestor to an already-cached class.
142
142
  this[_storage].derivedClassMap = undefined;
143
143
  }
144
144
  // --- Internal helpers used by view objects ---
@@ -228,12 +228,18 @@ export class SchemaView {
228
228
  // 1. Base class (recursive, depth-first)
229
229
  if (cls.baseClassIdx !== -1)
230
230
  this._collectProperties(cls.baseClassIdx, merged);
231
- // 2. Mixins in declaration order
231
+ // 2. Mixins in declaration order. Each mixin is resolved on its own so its overrides win over its
232
+ // ancestors, then only adds names not already provided by the base class or an earlier mixin.
232
233
  for (let i = 0; i < cls.mixinCount; i++) {
233
234
  const mixinIdx = this[_storage].classMixins[cls.mixinStartIdx + i];
234
235
  if (mixinIdx === -1 || mixinIdx === undefined)
235
236
  continue; // safety: dangling mixin ref
236
- this._collectMixinProperties(mixinIdx, merged);
237
+ const mixinProperties = new Map();
238
+ this._collectProperties(mixinIdx, mixinProperties);
239
+ for (const [nameKey, resolved] of mixinProperties) {
240
+ if (!merged.has(nameKey))
241
+ merged.set(nameKey, resolved);
242
+ }
237
243
  }
238
244
  // 3. Own properties - always override
239
245
  for (let i = 0; i < cls.ownPropCount; i++) {
@@ -242,27 +248,62 @@ export class SchemaView {
242
248
  merged.set(nameKey, { ref, classIdx });
243
249
  }
244
250
  }
245
- _collectMixinProperties(mixinIdx, merged) {
246
- const mixin = this[_storage].classes[mixinIdx];
247
- if (mixin === undefined)
248
- return; // safety: dangling index from excluded schema
249
- // Walk mixin's own base chain (mixins can extend other mixins)
250
- if (mixin.baseClassIdx !== -1)
251
- this._collectMixinProperties(mixin.baseClassIdx, merged);
252
- // Mixin's own mixins
253
- for (let i = 0; i < mixin.mixinCount; i++) {
254
- const subMixinIdx = this[_storage].classMixins[mixin.mixinStartIdx + i];
255
- if (subMixinIdx === -1 || subMixinIdx === undefined)
251
+ /** Finds the property declaration that a property of the given class overrides.
252
+ * Searches the base class and its ancestors first, then each mixin in declaration order.
253
+ */
254
+ _findOverriddenProperty(classIdx, nameKey) {
255
+ const cls = this[_storage].classes[classIdx];
256
+ if (cls === undefined || cls.type === ClassType.View)
257
+ return undefined;
258
+ if (cls.baseClassIdx !== -1) {
259
+ const found = this._findPropertyDeclaration(cls.baseClassIdx, nameKey);
260
+ if (found !== undefined)
261
+ return found;
262
+ }
263
+ for (let i = 0; i < cls.mixinCount; i++) {
264
+ const mixinIdx = this[_storage].classMixins[cls.mixinStartIdx + i];
265
+ if (mixinIdx === -1 || mixinIdx === undefined)
256
266
  continue; // safety: dangling mixin ref
257
- this._collectMixinProperties(subMixinIdx, merged);
267
+ const found = this._findPropertyDeclaration(mixinIdx, nameKey);
268
+ if (found !== undefined)
269
+ return found;
270
+ }
271
+ return undefined;
272
+ }
273
+ _findPropertyDeclaration(classIdx, nameKey) {
274
+ const cls = this[_storage].classes[classIdx];
275
+ if (cls === undefined)
276
+ return undefined; // safety: dangling index from excluded schema
277
+ for (let i = 0; i < cls.ownPropCount; i++) {
278
+ const ref = this[_storage].propertyRefs[cls.ownPropStart + i];
279
+ if (this[_storage].lowerStrings[this[_storage].propDefs[ref.defIdx].nameStringIdx] === nameKey)
280
+ return { ref, classIdx };
258
281
  }
259
- // Mixin's own properties - only set if not already present (first mixin wins)
260
- for (let i = 0; i < mixin.ownPropCount; i++) {
261
- const ref = this[_storage].propertyRefs[mixin.ownPropStart + i];
282
+ return this._findOverriddenProperty(classIdx, nameKey);
283
+ }
284
+ /** Returns the first declaration matching `predicate`, starting with the given declaration and
285
+ * continuing with the declarations it overrides.
286
+ * @internal
287
+ */
288
+ findPropertyDefInOverrides(ref, classIdx, predicate) {
289
+ let current = { ref, classIdx };
290
+ while (current !== null) {
291
+ const def = this[_storage].propDefs[current.ref.defIdx];
292
+ if (predicate(def))
293
+ return def;
294
+ current = this._getOverriddenProperty(current.ref, current.classIdx);
295
+ }
296
+ return undefined;
297
+ }
298
+ /** Returns the declaration the given one overrides, resolving and storing it on the ref on first access.
299
+ * Caching per ref is sound because a ref belongs to exactly one class; view properties (classIdx -1) never override.
300
+ */
301
+ _getOverriddenProperty(ref, classIdx) {
302
+ if (ref.overriddenProperty === undefined) {
262
303
  const nameKey = this[_storage].lowerStrings[this[_storage].propDefs[ref.defIdx].nameStringIdx];
263
- if (!merged.has(nameKey))
264
- merged.set(nameKey, { ref, classIdx: mixinIdx });
304
+ ref.overriddenProperty = classIdx !== -1 ? this._findOverriddenProperty(classIdx, nameKey) ?? null : null;
265
305
  }
306
+ return ref.overriddenProperty;
266
307
  }
267
308
  /** @internal */
268
309
  buildDerivedClassMap() {
@@ -287,6 +328,9 @@ export class SchemaView {
287
328
  // =====================================================================================
288
329
  // SchemaView namespace - flyweight view types
289
330
  // =====================================================================================
331
+ function hasCategory(def) { return def.categoryIdx !== -1; }
332
+ function hasKindOfQuantity(def) { return def.koqIdx !== -1; }
333
+ function isHiddenDef(def) { return def.isHidden; }
290
334
  /** @beta */
291
335
  (function (SchemaView) {
292
336
  /** Lightweight view over a schema in a `SchemaView`. Holds only a view reference and
@@ -630,10 +674,21 @@ export class SchemaView {
630
674
  }
631
675
  get kind() { return this._def.kind; }
632
676
  get isReadOnly() { return this._def.isReadOnly; }
633
- /** Reflects the `HiddenProperty` custom attribute from `CoreCustomAttributes`.
634
- * Properties marked hidden are typically excluded from UI display but remain accessible programmatically. */
677
+ /** Reflects the `HiddenProperty` custom attribute from `CoreCustomAttributes` on this declaration only;
678
+ * see `isEffectivelyHidden`. Properties marked hidden are typically excluded from UI display but remain
679
+ * accessible programmatically. */
635
680
  get isHidden() { return this._def.isHidden; }
636
- /** Display priority. Higher values should be displayed more prominently. 0 means default. */
681
+ /** True if this declaration is hidden. If it is not and the property is an override, we take this from the
682
+ * overridden property. Limitation: `HiddenProperty(Show=true)` reads the same as no `HiddenProperty`, so it does not
683
+ * unhide a property whose overridden property is hidden. */
684
+ get isEffectivelyHidden() {
685
+ if (this._def.isHidden)
686
+ return true;
687
+ return this._ctx.findPropertyDefInOverrides(this._ref, this._classIdx, isHiddenDef) !== undefined;
688
+ }
689
+ /** Display priority. Higher values should be displayed more prominently. 0 means default.
690
+ * Not inherited from the overridden property: an omitted priority is stored as 0, which cannot be told
691
+ * apart from an explicit 0 that overrides a base priority. */
637
692
  get priority() { return this._ref.priority; }
638
693
  /** The class that declared or contributed this property through inheritance.
639
694
  * For own properties, returns the class itself. For inherited properties, returns the
@@ -644,11 +699,21 @@ export class SchemaView {
644
699
  get declaringClass() {
645
700
  return this._classIdx !== -1 ? createClass(this._ctx, this._classIdx) : undefined;
646
701
  }
647
- /** Property category, or undefined if none assigned. Available on all property kinds. */
702
+ /** Property category. If there is no local category and the property is an override, we take this from
703
+ * the overridden property. Available on all property kinds. */
648
704
  get category() {
649
- const idx = this._def.categoryIdx;
705
+ let idx = this._def.categoryIdx;
706
+ if (idx === -1 && this._ctx[_storage].propCategories.length !== 0)
707
+ idx = this._ctx.findPropertyDefInOverrides(this._ref, this._classIdx, hasCategory)?.categoryIdx ?? -1;
650
708
  return idx !== -1 ? new PropertyCategory(this._ctx, idx) : undefined;
651
709
  }
710
+ /** @internal */
711
+ get _kindOfQuantity() {
712
+ let idx = this._def.koqIdx;
713
+ if (idx === -1 && this._ctx[_storage].koqs.length !== 0)
714
+ idx = this._ctx.findPropertyDefInOverrides(this._ref, this._classIdx, hasKindOfQuantity)?.koqIdx ?? -1;
715
+ return idx !== -1 ? new KindOfQuantity(this._ctx, idx) : undefined;
716
+ }
652
717
  // Type guards - real type predicates that narrow `this`
653
718
  /** True for `SchemaView.PrimitiveProperty` and `SchemaView.PrimitiveArrayProperty`.
654
719
  * Matches ecschema-metadata behavior where `isPrimitive()` includes primitive arrays. */
@@ -710,10 +775,9 @@ export class SchemaView {
710
775
  const idx = this._def.enumIdx;
711
776
  return idx !== -1 ? new Enumeration(this._ctx, idx) : undefined;
712
777
  }
713
- get kindOfQuantity() {
714
- const idx = this._def.koqIdx;
715
- return idx !== -1 ? new KindOfQuantity(this._ctx, idx) : undefined;
716
- }
778
+ /** KindOfQuantity. If there is no local KindOfQuantity and the property is an override, we take this
779
+ * from the overridden property. */
780
+ get kindOfQuantity() { return this._kindOfQuantity; }
717
781
  }
718
782
  SchemaView.PrimitiveProperty = PrimitiveProperty;
719
783
  /** An array of primitive values. Same primitive/enum fields as `PrimitiveProperty`,
@@ -730,10 +794,9 @@ export class SchemaView {
730
794
  const idx = this._def.enumIdx;
731
795
  return idx !== -1 ? new Enumeration(this._ctx, idx) : undefined;
732
796
  }
733
- get kindOfQuantity() {
734
- const idx = this._def.koqIdx;
735
- return idx !== -1 ? new KindOfQuantity(this._ctx, idx) : undefined;
736
- }
797
+ /** KindOfQuantity. If there is no local KindOfQuantity and the property is an override, we take this
798
+ * from the overridden property. */
799
+ get kindOfQuantity() { return this._kindOfQuantity; }
737
800
  get arrayMinOccurs() { return this._def.arrayMinOccurs; }
738
801
  get arrayMaxOccurs() { return this._def.arrayMaxOccurs; }
739
802
  }