mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-26 03:31:12 +08:00
feat: PlainMonthDay/PlainYearMonth 对象地基——构造器/getter/toString/toJSON 注册与 BuiltinWorld 接线
This commit is contained in:
@@ -957,6 +957,150 @@ pub fn bind_temporal(core: &Arc<KernelCore>, session: &KernelSession, global: &m
|
||||
PropAttributes::new(false, false, true),
|
||||
);
|
||||
|
||||
// Temporal.PlainMonthDay:构造器、3 个分量 getter 与 toString/toJSON。
|
||||
let plain_month_day_ctor_ptr = world.plain_month_day_constructor.as_ptr() as *mut JsObject;
|
||||
let plain_month_day_ctor = unsafe { &mut *plain_month_day_ctor_ptr };
|
||||
configure_native_constructor(
|
||||
plain_month_day_ctor,
|
||||
oxide_builtins::temporal::plain_month_day_constructor::<crate::vm::Vm> as *const (),
|
||||
2,
|
||||
);
|
||||
let length_si = core.perm_interner().intern("length").0;
|
||||
let length_shape = core.shape_forge().make_shape(plain_month_day_ctor.shape_id(), length_si);
|
||||
plain_month_day_ctor.set_shape_id(length_shape);
|
||||
plain_month_day_ctor.ensure_hash_props().push(JsValue::int(2));
|
||||
let length_pos = plain_month_day_ctor
|
||||
.hash_props_vec()
|
||||
.map_or(0, |props| props.len() as u32)
|
||||
.saturating_sub(1);
|
||||
plain_month_day_ctor.set_data_meta(length_pos, PropAttributes::new(false, false, true));
|
||||
|
||||
let plain_month_day_proto_ptr = world.plain_month_day_proto.as_ptr() as *mut JsObject;
|
||||
let plain_month_day_proto = unsafe { &mut *plain_month_day_proto_ptr };
|
||||
for (name, getter) in [
|
||||
("day", oxide_builtins::temporal::plain_month_day_day::<crate::vm::Vm> as *const ()),
|
||||
(
|
||||
"monthCode",
|
||||
oxide_builtins::temporal::plain_month_day_month_code::<crate::vm::Vm> as *const (),
|
||||
),
|
||||
(
|
||||
"calendarId",
|
||||
oxide_builtins::temporal::plain_month_day_calendar_id::<crate::vm::Vm> as *const (),
|
||||
),
|
||||
] {
|
||||
bind_accessor_getter(core, session, plain_month_day_proto, name, getter);
|
||||
}
|
||||
apply_binding_table(
|
||||
world,
|
||||
plain_month_day_proto,
|
||||
core,
|
||||
&[
|
||||
(
|
||||
"toString",
|
||||
oxide_builtins::temporal::plain_month_day_to_string::<crate::vm::Vm> as *const (),
|
||||
0,
|
||||
),
|
||||
(
|
||||
"toJSON",
|
||||
oxide_builtins::temporal::plain_month_day_to_json::<crate::vm::Vm> as *const (),
|
||||
0,
|
||||
),
|
||||
],
|
||||
);
|
||||
bind_well_known_data_property(
|
||||
core,
|
||||
plain_month_day_proto,
|
||||
9,
|
||||
JsValue::perm_string(
|
||||
core.perm_interner()
|
||||
.string_ptr(core.perm_interner().intern("Temporal.PlainMonthDay").0),
|
||||
),
|
||||
PropAttributes::new(false, false, true),
|
||||
);
|
||||
|
||||
// Temporal.PlainYearMonth:构造器、10 个 getter 与 toString/toJSON。
|
||||
let plain_year_month_ctor_ptr = world.plain_year_month_constructor.as_ptr() as *mut JsObject;
|
||||
let plain_year_month_ctor = unsafe { &mut *plain_year_month_ctor_ptr };
|
||||
configure_native_constructor(
|
||||
plain_year_month_ctor,
|
||||
oxide_builtins::temporal::plain_year_month_constructor::<crate::vm::Vm> as *const (),
|
||||
2,
|
||||
);
|
||||
let length_si = core.perm_interner().intern("length").0;
|
||||
let length_shape = core.shape_forge().make_shape(plain_year_month_ctor.shape_id(), length_si);
|
||||
plain_year_month_ctor.set_shape_id(length_shape);
|
||||
plain_year_month_ctor.ensure_hash_props().push(JsValue::int(2));
|
||||
let length_pos = plain_year_month_ctor
|
||||
.hash_props_vec()
|
||||
.map_or(0, |props| props.len() as u32)
|
||||
.saturating_sub(1);
|
||||
plain_year_month_ctor.set_data_meta(length_pos, PropAttributes::new(false, false, true));
|
||||
|
||||
let plain_year_month_proto_ptr = world.plain_year_month_proto.as_ptr() as *mut JsObject;
|
||||
let plain_year_month_proto = unsafe { &mut *plain_year_month_proto_ptr };
|
||||
for (name, getter) in [
|
||||
("year", oxide_builtins::temporal::plain_year_month_year::<crate::vm::Vm> as *const ()),
|
||||
("month", oxide_builtins::temporal::plain_year_month_month::<crate::vm::Vm> as *const ()),
|
||||
(
|
||||
"monthCode",
|
||||
oxide_builtins::temporal::plain_year_month_month_code::<crate::vm::Vm> as *const (),
|
||||
),
|
||||
(
|
||||
"calendarId",
|
||||
oxide_builtins::temporal::plain_year_month_calendar_id::<crate::vm::Vm> as *const (),
|
||||
),
|
||||
(
|
||||
"daysInMonth",
|
||||
oxide_builtins::temporal::plain_year_month_days_in_month::<crate::vm::Vm> as *const (),
|
||||
),
|
||||
(
|
||||
"daysInYear",
|
||||
oxide_builtins::temporal::plain_year_month_days_in_year::<crate::vm::Vm> as *const (),
|
||||
),
|
||||
(
|
||||
"monthsInYear",
|
||||
oxide_builtins::temporal::plain_year_month_months_in_year::<crate::vm::Vm> as *const (),
|
||||
),
|
||||
(
|
||||
"inLeapYear",
|
||||
oxide_builtins::temporal::plain_year_month_in_leap_year::<crate::vm::Vm> as *const (),
|
||||
),
|
||||
("era", oxide_builtins::temporal::plain_year_month_era::<crate::vm::Vm> as *const ()),
|
||||
(
|
||||
"eraYear",
|
||||
oxide_builtins::temporal::plain_year_month_era_year::<crate::vm::Vm> as *const (),
|
||||
),
|
||||
] {
|
||||
bind_accessor_getter(core, session, plain_year_month_proto, name, getter);
|
||||
}
|
||||
apply_binding_table(
|
||||
world,
|
||||
plain_year_month_proto,
|
||||
core,
|
||||
&[
|
||||
(
|
||||
"toString",
|
||||
oxide_builtins::temporal::plain_year_month_to_string::<crate::vm::Vm> as *const (),
|
||||
0,
|
||||
),
|
||||
(
|
||||
"toJSON",
|
||||
oxide_builtins::temporal::plain_year_month_to_json::<crate::vm::Vm> as *const (),
|
||||
0,
|
||||
),
|
||||
],
|
||||
);
|
||||
bind_well_known_data_property(
|
||||
core,
|
||||
plain_year_month_proto,
|
||||
9,
|
||||
JsValue::perm_string(
|
||||
core.perm_interner()
|
||||
.string_ptr(core.perm_interner().intern("Temporal.PlainYearMonth").0),
|
||||
),
|
||||
PropAttributes::new(false, false, true),
|
||||
);
|
||||
|
||||
// 把子对象挂到 Temporal 命名空间对象上,再把 Temporal 挂到 global。
|
||||
bind_global_value(core, temporal, "Now", JsValue::from_js_object(now_ptr));
|
||||
bind_global_value(core, temporal, "Instant", JsValue::from_js_object(instant_ctor_ptr));
|
||||
@@ -965,5 +1109,7 @@ pub fn bind_temporal(core: &Arc<KernelCore>, session: &KernelSession, global: &m
|
||||
bind_global_value(core, temporal, "PlainDateTime", JsValue::from_js_object(plain_date_time_ctor_ptr));
|
||||
bind_global_value(core, temporal, "Duration", JsValue::from_js_object(duration_ctor_ptr));
|
||||
bind_global_value(core, temporal, "ZonedDateTime", JsValue::from_js_object(zoned_date_time_ctor_ptr));
|
||||
bind_global_value(core, temporal, "PlainMonthDay", JsValue::from_js_object(plain_month_day_ctor_ptr));
|
||||
bind_global_value(core, temporal, "PlainYearMonth", JsValue::from_js_object(plain_year_month_ctor_ptr));
|
||||
bind_global_value(core, global, "Temporal", JsValue::from_js_object(temporal_ptr));
|
||||
}
|
||||
|
||||
@@ -2604,3 +2604,196 @@ fn duration_total_relative_zdt() {
|
||||
.unwrap();
|
||||
assert_eq!(r.as_double(), 1.0);
|
||||
}
|
||||
|
||||
// -- Temporal.PlainMonthDay / PlainYearMonth(E 批)--
|
||||
|
||||
#[test]
|
||||
fn plain_month_day_ctor_basic_and_instanceof() {
|
||||
let mut vm = Vm::new();
|
||||
// 构造成功:instanceof / length / name / 槽值 / 参考年默认 1972 经 always 形回读。
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"(() => {
|
||||
const md = new Temporal.PlainMonthDay(5, 2);
|
||||
return [
|
||||
md instanceof Temporal.PlainMonthDay,
|
||||
Temporal.PlainMonthDay.length,
|
||||
Temporal.PlainMonthDay.name,
|
||||
Temporal.PlainYearMonth.length,
|
||||
md.monthCode,
|
||||
md.day,
|
||||
md.calendarId,
|
||||
md.toString({calendarName: 'always'}),
|
||||
].join('|');
|
||||
})()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(str_val(&vm, r), "true|2|PlainMonthDay|2|M05|2|iso8601|1972-05-02[u-ca=iso8601]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_month_day_ctor_rejects() {
|
||||
let mut vm = Vm::new();
|
||||
// BigInt/Symbol 分量 → TypeError;NaN/±Inf → RangeError;缺参 → RangeError;
|
||||
// 非闰参考年 2/29 → RangeError,闰参考年 1972 合法;非 new 调用 → TypeError。
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"(() => {
|
||||
const kind = (fn) => { try { fn(); return 'no'; } catch (e) { return e.constructor.name; } };
|
||||
return [
|
||||
kind(() => new Temporal.PlainMonthDay(1n, 1)),
|
||||
kind(() => new Temporal.PlainMonthDay(Symbol(), 1)),
|
||||
kind(() => new Temporal.PlainMonthDay(NaN, 1)),
|
||||
kind(() => new Temporal.PlainMonthDay(1, Infinity)),
|
||||
kind(() => new Temporal.PlainMonthDay()),
|
||||
kind(() => new Temporal.PlainMonthDay(2, 29, 'iso8601', 2023)),
|
||||
new Temporal.PlainMonthDay(2, 29).day,
|
||||
kind(() => Temporal.PlainMonthDay(1, 2)),
|
||||
].join('|');
|
||||
})()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
str_val(&vm, r),
|
||||
"TypeError|TypeError|RangeError|RangeError|RangeError|RangeError|29|TypeError"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_month_day_ctor_calendar_argument() {
|
||||
let mut vm = Vm::new();
|
||||
// 日历参数:{} → TypeError;函数对象按缺省 iso8601;大小写不敏感白名单;
|
||||
// 未知字符串 → RangeError。
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"(() => {
|
||||
const kind = (fn) => { try { fn(); return 'no'; } catch (e) { return e.constructor.name; } };
|
||||
return [
|
||||
kind(() => new Temporal.PlainMonthDay(1, 1, {})),
|
||||
new Temporal.PlainMonthDay(1, 1, () => 'iso8601').calendarId,
|
||||
new Temporal.PlainMonthDay(1, 1, 'iSo8601').calendarId,
|
||||
kind(() => new Temporal.PlainMonthDay(1, 1, 'local')),
|
||||
].join('|');
|
||||
})()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(str_val(&vm, r), "TypeError|iso8601|iso8601|RangeError");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_year_month_ctor_limits_and_ref_day() {
|
||||
let mut vm = Vm::new();
|
||||
// 年月边界:-271821-03 与 275760-10 越界抛 RangeError,相邻月合法;
|
||||
// refISODay 缺省 1,须落在目标月长内;非 new 调用 → TypeError;
|
||||
// NaN 分量(undefined/不可解析串)→ RangeError,null → 0。
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"(() => {
|
||||
const kind = (fn) => { try { fn(); return 'no'; } catch (e) { return e.constructor.name; } };
|
||||
const ym = new Temporal.PlainYearMonth(-271821, 4, 'iso8601', 18);
|
||||
return [
|
||||
kind(() => new Temporal.PlainYearMonth(-271821, 3)),
|
||||
kind(() => new Temporal.PlainYearMonth(275760, 10)),
|
||||
new Temporal.PlainYearMonth(-271821, 4).monthCode,
|
||||
new Temporal.PlainYearMonth(275760, 9).monthCode,
|
||||
ym.toString({calendarName: 'always'}),
|
||||
new Temporal.PlainYearMonth(2021, 2).toString({calendarName: 'always'}),
|
||||
kind(() => new Temporal.PlainYearMonth(2021, 2, 'iso8601', 0)),
|
||||
kind(() => new Temporal.PlainYearMonth(2021, 2, 'iso8601', 29)),
|
||||
kind(() => new Temporal.PlainYearMonth(undefined, 11)),
|
||||
kind(() => new Temporal.PlainYearMonth('invalid', 11)),
|
||||
new Temporal.PlainYearMonth(null, 11).year,
|
||||
kind(() => new Temporal.PlainMonthDay(undefined, 24)),
|
||||
kind(() => Temporal.PlainYearMonth(1970, 1)),
|
||||
].join('|');
|
||||
})()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
str_val(&vm, r),
|
||||
"RangeError|RangeError|M04|M09|-271821-04-18[u-ca=iso8601]|2021-02-01[u-ca=iso8601]|RangeError|RangeError|RangeError|RangeError|0|RangeError|TypeError"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_year_month_getters() {
|
||||
let mut vm = Vm::new();
|
||||
// 槽值 getter + 日历直读 + ISO 日历无纪元(era/eraYear 恒 undefined)。
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"(() => {
|
||||
const ym = new Temporal.PlainYearMonth(-1, 8);
|
||||
return [
|
||||
ym.year, ym.month, ym.monthCode, ym.calendarId,
|
||||
ym.daysInMonth, ym.daysInYear, ym.monthsInYear, ym.inLeapYear,
|
||||
String(ym.era), String(ym.eraYear),
|
||||
].join('|');
|
||||
})()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(str_val(&vm, r), "-1|8|M08|iso8601|31|365|12|false|undefined|undefined");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_month_day_to_string_and_to_json() {
|
||||
let mut vm = Vm::new();
|
||||
// 默认/auto/never 同形;critical 带 ! 注解;toJSON = 默认形且忽略参数;
|
||||
// 非法 calendarName → RangeError;options 非对象 → TypeError。
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"(() => {
|
||||
const md = new Temporal.PlainMonthDay(5, 2);
|
||||
const kind = (fn) => { try { fn(); return 'no'; } catch (e) { return e.constructor.name; } };
|
||||
return [
|
||||
md.toString(),
|
||||
md.toString({calendarName: 'auto'}),
|
||||
md.toString({calendarName: 'never'}),
|
||||
md.toString({calendarName: 'critical'}),
|
||||
md.toJSON(),
|
||||
kind(() => md.toString({calendarName: 'ALWAYS'})),
|
||||
kind(() => md.toString(1)),
|
||||
md.toString({}),
|
||||
].join('|');
|
||||
})()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
str_val(&vm, r),
|
||||
"05-02|05-02|05-02|1972-05-02[!u-ca=iso8601]|05-02|RangeError|TypeError|05-02"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_year_month_to_string_year_format() {
|
||||
let mut vm = Vm::new();
|
||||
// 年份格式化:-1 → -000001(6 位带符号);always 形补参考日。
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"(() => {
|
||||
const ym = new Temporal.PlainYearMonth(-1, 8);
|
||||
return [ym.toString(), ym.toString({calendarName: 'always'}), ym.toJSON()].join('|');
|
||||
})()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(str_val(&vm, r), "-000001-08|-000001-08-01[u-ca=iso8601]|-000001-08");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_month_day_and_year_month_branding() {
|
||||
let mut vm = Vm::new();
|
||||
// receiver 非实例 → TypeError(toString / getter 均校验品牌)。
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"(() => {
|
||||
const kind = (fn) => { try { fn(); return 'no'; } catch (e) { return e.constructor.name; } };
|
||||
return [
|
||||
kind(() => Temporal.PlainMonthDay.prototype.toString.call(1)),
|
||||
kind(() => Temporal.PlainMonthDay.prototype.day.call({})),
|
||||
kind(() => Temporal.PlainYearMonth.prototype.toString.call('x')),
|
||||
kind(() => Temporal.PlainYearMonth.prototype.year.call(null)),
|
||||
].join('|');
|
||||
})()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(str_val(&vm, r), "TypeError|TypeError|TypeError|TypeError");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user