use boa_engine::{Context, JsResult};
pub struct ES202XFeatures;
impl ES202XFeatures {
pub fn init(context: &mut Context) -> JsResult<()> {
Self::init_optional_chaining(context)?;
Self::init_nullish_coalescing(context)?;
Self::init_private_class_fields(context)?;
Self::init_array_findlast(context)?;
Self::init_regexp_match_indices(context)?;
Self::init_temporal_api(context)?;
Ok(())
}
fn init_optional_chaining(_context: &mut Context) -> JsResult<()> {
Ok(())
}
fn init_nullish_coalescing(_context: &mut Context) -> JsResult<()> {
Ok(())
}
fn init_private_class_fields(_context: &mut Context) -> JsResult<()> {
Ok(())
}
fn init_array_findlast(context: &mut Context) -> JsResult<()> {
let findlast_code = r#"
if (!Array.prototype.findLast) {
Array.prototype.findLast = function(predicate, thisArg) {
if (this == null) {
throw new TypeError('Array.prototype.findLast called on null or undefined');
}
if (typeof predicate !== 'function') {
throw new TypeError('predicate must be a function');
}
var list = Object(this);
var length = list.length >>> 0;
for (var i = length - 1; i >= 0; i--) {
if (i in list) {
var value = list[i];
if (predicate.call(thisArg, value, i, list)) {
return value;
}
}
}
return undefined;
};
}
"#;
context.eval(boa_engine::Source::from_bytes(findlast_code.as_bytes()))?;
let findlastindex_code = r#"
if (!Array.prototype.findLastIndex) {
Array.prototype.findLastIndex = function(predicate, thisArg) {
if (this == null) {
throw new TypeError('Array.prototype.findLastIndex called on null or undefined');
}
if (typeof predicate !== 'function') {
throw new TypeError('predicate must be a function');
}
var list = Object(this);
var length = list.length >>> 0;
for (var i = length - 1; i >= 0; i--) {
if (i in list) {
var value = list[i];
if (predicate.call(thisArg, value, i, list)) {
return i;
}
}
}
return -1;
};
}
"#;
context.eval(boa_engine::Source::from_bytes(findlastindex_code.as_bytes()))?;
Ok(())
}
fn init_regexp_match_indices(context: &mut Context) -> JsResult<()> {
let regexp_code = r#"
(function() {
// Check if the d flag is supported
var hasIndicesSupport = (() => {
try {
new RegExp('', 'd');
return true;
} catch (_) {
return false;
}
})();
if (!hasIndicesSupport) {
// Override RegExp constructor to add d flag support
var OriginalRegExp = window.RegExp;
var RegExp = function(pattern, flags) {
var hasIndices = flags && flags.includes('d');
if (hasIndices) {
// Remove the d flag for the native RegExp
flags = flags.replace('d', '');
}
var regex = new OriginalRegExp(pattern, flags);
if (hasIndices) {
// Add custom handling for match indices
regex.hasIndices = true;
var originalExec = regex.exec;
regex.exec = function(str) {
var match = originalExec.call(this, str);
if (match && this.hasIndices) {
match.indices = this.getIndices(match);
}
return match;
};
regex.getIndices = function(match) {
var indices = [];
for (var i = 0; i < match.length; i++) {
if (match[i] !== undefined) {
indices.push([match.index, match.index + match[i].length]);
} else {
indices.push(undefined);
}
}
return indices;
};
}
return regex;
};
RegExp.prototype = OriginalRegExp.prototype;
RegExp.prototype.constructor = RegExp;
window.RegExp = RegExp;
}
})();
"#;
context.eval(boa_engine::Source::from_bytes(regexp_code.as_bytes()))?;
Ok(())
}
fn init_temporal_api(context: &mut Context) -> JsResult<()> {
let temporal_code = r#"
// Simplified Temporal API implementation
if (typeof globalThis.Temporal === 'undefined') {
globalThis.Temporal = {};
// Temporal.Now
Temporal.Now = {
// Simplified implementation - in a real polyfill, this would use timezone data
instant: function() {
return {
epochMilliseconds: Date.now(),
epochNanoseconds: Date.now() * 1000000n,
toString: function() {
return new Date(this.epochMilliseconds).toISOString();
}
};
},
// Simplified implementation without timezone support
timeZone: function() {
return {
toString: function() {
return Intl.DateTimeFormat().resolvedOptions().timeZone;
}
};
},
// Simplified date implementation
plainDate: function(calendar) {
var now = new Date();
return {
year: now.getFullYear(),
month: now.getMonth() + 1,
day: now.getDate(),
calendar: calendar || 'iso8601',
toString: function() {
return `${this.year}-${this.month.toString().padStart(2, '0')}-${this.day.toString().padStart(2, '0')}`;
}
};
},
// Simplified time implementation
plainTime: function() {
var now = new Date();
return {
hour: now.getHours(),
minute: now.getMinutes(),
second: now.getSeconds(),
millisecond: now.getMilliseconds(),
toString: function() {
return `${this.hour.toString().padStart(2, '0')}:${this.minute.toString().padStart(2, '0')}:${this.second.toString().padStart(2, '0')}`;
}
};
}
};
// Temporal.PlainDate
Temporal.PlainDate = function(year, month, day, calendar) {
return {
year: year,
month: month,
day: day,
calendar: calendar || 'iso8601',
toString: function() {
return `${this.year}-${this.month.toString().padStart(2, '0')}-${this.day.toString().padStart(2, '0')}`;
}
};
};
// Temporal.PlainTime
Temporal.PlainTime = function(hour, minute, second, millisecond) {
return {
hour: hour || 0,
minute: minute || 0,
second: second || 0,
millisecond: millisecond || 0,
toString: function() {
return `${this.hour.toString().padStart(2, '0')}:${this.minute.toString().padStart(2, '0')}:${this.second.toString().padStart(2, '0')}`;
}
};
};
// Temporal.Duration
Temporal.Duration = function(years, months, weeks, days, hours, minutes, seconds, milliseconds) {
return {
years: years || 0,
months: months || 0,
weeks: weeks || 0,
days: days || 0,
hours: hours || 0,
minutes: minutes || 0,
seconds: seconds || 0,
milliseconds: milliseconds || 0,
toString: function() {
let result = 'P';
if (this.years) result += `${this.years}Y`;
if (this.months) result += `${this.months}M`;
if (this.weeks) result += `${this.weeks}W`;
if (this.days) result += `${this.days}D`;
if (this.hours || this.minutes || this.seconds) {
result += 'T';
if (this.hours) result += `${this.hours}H`;
if (this.minutes) result += `${this.minutes}M`;
if (this.seconds) result += `${this.seconds}S`;
}
return result;
}
};
};
}
"#;
context.eval(boa_engine::Source::from_bytes(temporal_code.as_bytes()))?;
Ok(())
}
}