Skip to content

Commit

Permalink
FEAT: Implement Symbol.unscopables
Browse files Browse the repository at this point in the history
  • Loading branch information
0xe committed Aug 7, 2024
1 parent cc302b4 commit 4ee8718
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 3 deletions.
36 changes: 35 additions & 1 deletion rhino/src/main/java/org/mozilla/javascript/NativeArray.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,17 @@ public class NativeArray extends IdScriptableObject implements List {

private static final Object ARRAY_TAG = "Array";
private static final Long NEGATIVE_ONE = Long.valueOf(-1);
private static final String[] UNSCOPABLES = {
"at", "copyWithin", "entries", "fill", "find", "findIndex", "findLast",
"findLastIndex", "flat", "flatMap", "includes", "keys", "toReversed",
"toSorted", "toSpliced", "values"
};

static void init(Context cx, Scriptable scope, boolean sealed) {
NativeArray obj = new NativeArray(0);
IdFunctionObject constructor = obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed);
ScriptRuntimeES6.addSymbolSpecies(cx, scope, constructor);
ScriptRuntimeES6.addSymbolUnscopables(cx, scope, constructor);
}

static int getMaximumInitialCapacity() {
Expand Down Expand Up @@ -160,6 +166,11 @@ protected void fillConstructorProperties(IdFunctionObject ctor) {

@Override
protected void initPrototypeId(int id) {
if (id == SymbolId_unscopables) {
initPrototypeValue(id, SymbolKey.UNSCOPABLES, makeUnscopables(), DONTENUM | READONLY);
return;
}

String s, fnName = null;
int arity;
switch (id) {
Expand Down Expand Up @@ -502,6 +513,25 @@ public void setPrototype(Scriptable p) {
}
}

private Object makeUnscopables() {
Context cx = Context.getCurrentContext();
NativeObject obj;

if (cx != null) {
Scriptable scope = this.getParentScope();
obj = (NativeObject) cx.newObject(scope);
} else {
obj = new NativeObject();
}

ScriptableObject desc = ScriptableObject.buildDataDescriptor(obj, true, EMPTY);
for (var k: UNSCOPABLES) {
obj.defineOwnProperty(cx, k, desc);
}
obj.setPrototype(null); // unscopables don't have any prototype
return obj;
}

@Override
public Object get(int index, Scriptable start) {
if (!denseOnly && isGetterOrSetter(null, index, false)) return super.get(index, start);
Expand Down Expand Up @@ -2382,6 +2412,8 @@ protected int findPrototypeId(Symbol k) {
// as the "values" property. We implement this by returning the
// ID of "values" when the iterator symbol is accessed.
return Id_values;
} else if (SymbolKey.UNSCOPABLES.equals(k)) {
return SymbolId_unscopables;
}
return 0;
}
Expand Down Expand Up @@ -2533,7 +2565,9 @@ protected int findPrototypeId(String s) {
Id_at = 32,
Id_flat = 33,
Id_flatMap = 34,
MAX_PROTOTYPE_ID = Id_flatMap;
SymbolId_unscopables = 35,
MAX_PROTOTYPE_ID = SymbolId_unscopables;

private static final int ConstructorId_join = -Id_join,
ConstructorId_reverse = -Id_reverse,
ConstructorId_sort = -Id_sort,
Expand Down
11 changes: 11 additions & 0 deletions rhino/src/main/java/org/mozilla/javascript/ScriptRuntimeES6.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,15 @@ public static void addSymbolSpecies(
thisObj));
constructor.defineOwnProperty(cx, SymbolKey.SPECIES, speciesDescriptor, false);
}

/**
* Registers the symbol <code>[Symbol.unscopables]</code> on the given constructor function.
*/
public static void addSymbolUnscopables(Context cx, Scriptable scope, IdScriptableObject constructor) {
ScriptableObject unScopablesDescriptor = (ScriptableObject) cx.newObject(scope);
ScriptableObject.putProperty(unScopablesDescriptor, "enumerable", false);
ScriptableObject.putProperty(unScopablesDescriptor, "configurable", false);
ScriptableObject.putProperty(unScopablesDescriptor, "writable", false);
constructor.defineOwnProperty(cx, SymbolKey.UNSCOPABLES, unScopablesDescriptor, false);
}
}
28 changes: 28 additions & 0 deletions tests/testsrc/jstests/es6/array.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,32 @@ assertEquals(a.map(_ => 'a'), ['a', 'a']);
assertEquals(Array, symbolSpeciesValue);
})();

(function TestSymbolUnScopables() {
var symbolUnscopablesValue = Array[Symbol.unscopables];
assertEquals(undefined, symbolUnscopablesValue);
})();

(function TestSymbolUnScopablesOnArray() {
var symbolValuesToAssert = '{' +
'"at":true,' +
'"copyWithin":true,' +
'"entries":true,' +
'"fill":true,' +
'"find":true,' +
'"findIndex":true,' +
'"findLast":true,' +
'"findLastIndex":true,' +
'"flat":true,' +
'"flatMap":true,' +
'"includes":true,' +
'"keys":true,' +
'"toReversed":true,' +
'"toSorted":true,' +
'"toSpliced":true,' +
'"values":true' +
'}';
var symbolValues = Array.prototype[Symbol.unscopables];
assertEquals(JSON.stringify(symbolValues), symbolValuesToAssert);
})();

"success";
3 changes: 1 addition & 2 deletions tests/testsrc/test262.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

~annexB

built-ins/Array 144/2670 (5.39%)
built-ins/Array 142/2670 (5.32%)
from/calling-from-valid-1-noStrict.js non-strict Spec pretty clearly says this should be undefined
from/elements-deleted-after.js Checking to see if length changed, but spec says it should not
from/iter-map-fn-this-non-strict.js non-strict Error propagation needs work in general
Expand Down Expand Up @@ -138,7 +138,6 @@ built-ins/Array 144/2670 (5.39%)
prototype/splice/set_length_no_args.js
prototype/splice/target-array-non-extensible.js
prototype/splice/target-array-with-non-configurable-property.js
prototype/Symbol.unscopables 2/2 (100.0%)
prototype/toLocaleString/primitive_this_value.js strict
prototype/toLocaleString/primitive_this_value_getter.js strict
prototype/unshift/throws-with-string-receiver.js
Expand Down

0 comments on commit 4ee8718

Please sign in to comment.