Skip to content
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 39 additions & 10 deletions packages/apidom-datamodel/src/primitives/Element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,13 @@ class Element implements ToValue, Equatable, Freezable {

/** Unique identifier for this element. */
get id(): Element {
return this.getMetaProperty('id', '');
if (this.isFrozen) {
return this.getMetaProperty('id', '') as Element;
}
if (!this.hasMetaProperty('id')) {
this.setMetaProperty('id', '');
}
return this.meta.get('id') as Element;
}

set id(value: Element | string) {
Expand All @@ -282,7 +288,13 @@ class Element implements ToValue, Equatable, Freezable {

/** CSS-like class names. */
get classes(): ArrayElement {
return this.getMetaProperty('classes', []) as ArrayElement;
if (this.isFrozen) {
return this.getMetaProperty('classes', []) as ArrayElement;
}
if (!this.hasMetaProperty('classes')) {
this.setMetaProperty('classes', []);
}
return this.meta.get('classes') as ArrayElement;
}

set classes(value: ArrayElement | unknown[]) {
Expand All @@ -291,7 +303,13 @@ class Element implements ToValue, Equatable, Freezable {

/** Hyperlinks associated with this element. */
get links(): ArrayElement {
return this.getMetaProperty('links', []) as ArrayElement;
if (this.isFrozen) {
return this.getMetaProperty('links', []) as ArrayElement;
}
if (!this.hasMetaProperty('links')) {
this.setMetaProperty('links', []);
}
return this.meta.get('links') as ArrayElement;
}

set links(value: ArrayElement | unknown[]) {
Expand Down Expand Up @@ -451,16 +469,27 @@ class Element implements ToValue, Equatable, Freezable {
}

/**
* Gets a meta property, creating it with default value if not present.
* Gets a meta property.
*
* When the property doesn't exist:
* - With defaultValue: returns a new refracted element instance (not cached)
* - Without defaultValue: returns undefined
*
* Note: Each call with a default creates a new instance. Use setMetaProperty
* first if you need reference equality across multiple accesses.
Comment on lines +478 to +479
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The doc note “Each call with a default creates a new instance” is misleading once the property exists (the stored value is returned and the default is ignored). Consider clarifying that this only applies when the property is missing, to avoid confusing callers about reference equality semantics.

Suggested change
* Note: Each call with a default creates a new instance. Use setMetaProperty
* first if you need reference equality across multiple accesses.
* Note: When the property is missing, each call that supplies a default
* creates a new instance (the value is not cached). Once the property
* exists, the stored value is returned and the default is ignored. Use
* setMetaProperty first if you need reference equality across multiple
* accesses.

Copilot uses AI. Check for mistakes.
*/
public getMetaProperty(name: string, defaultValue: unknown): Element {
if (!this.meta.hasKey(name)) {
if (this.isFrozen) {
const element = this.refract(defaultValue);
public getMetaProperty(name: string, defaultValue: unknown): Element;
public getMetaProperty(name: string): Element | undefined;
public getMetaProperty(name: string, defaultValue?: unknown): Element | undefined {
if (!this.hasMetaProperty(name)) {
if (defaultValue === undefined) {
Comment on lines +484 to +485
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getMetaProperty uses defaultValue === undefined to decide whether a default was provided. This makes getMetaProperty(name, undefined) indistinguishable from calling it without a default, which changes behavior compared to passing an explicit default. Use arguments.length (or similar) to distinguish “no default argument” from “default explicitly set to undefined”.

Suggested change
if (!this.hasMetaProperty(name)) {
if (defaultValue === undefined) {
const hasDefaultArg = arguments.length >= 2;
if (!this.hasMetaProperty(name)) {
if (!hasDefaultArg) {

Copilot uses AI. Check for mistakes.
return undefined;
}
const element = this.refract(defaultValue);
if (element && this.isFrozen) {
element.freeze();
return element;
}
this.meta.set(name, defaultValue);
return element;
}
return this.meta.get(name)!;
}
Comment on lines +481 to 495
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The overload getMetaProperty(name, defaultValue): Element is not guaranteed by the implementation: if the meta key exists with an explicit undefined value (possible via setMetaProperty/ObjectElement.set), this.meta.get(name) returns undefined, even when a defaultValue is provided. Either prevent storing undefined for meta properties, treat undefined values as “missing” when reading (so the default applies), or relax the overload return type to Element | undefined to match runtime behavior.

Copilot uses AI. Check for mistakes.
Expand Down
Loading