summaryrefslogtreecommitdiffstats
path: root/js/src/jit-test/tests/decorators
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jit-test/tests/decorators')
-rw-r--r--js/src/jit-test/tests/decorators/accessor-decorators.js189
-rw-r--r--js/src/jit-test/tests/decorators/accessors.js86
-rw-r--r--js/src/jit-test/tests/decorators/addInitializer.js39
-rw-r--r--js/src/jit-test/tests/decorators/class-decorators.js101
-rw-r--r--js/src/jit-test/tests/decorators/decorator-this.js36
-rw-r--r--js/src/jit-test/tests/decorators/field-decorators.js104
-rw-r--r--js/src/jit-test/tests/decorators/getter-setter-decorators.js68
-rw-r--r--js/src/jit-test/tests/decorators/method-decorators.js122
-rw-r--r--js/src/jit-test/tests/decorators/syntax.js172
9 files changed, 917 insertions, 0 deletions
diff --git a/js/src/jit-test/tests/decorators/accessor-decorators.js b/js/src/jit-test/tests/decorators/accessor-decorators.js
new file mode 100644
index 0000000000..33e9c8a728
--- /dev/null
+++ b/js/src/jit-test/tests/decorators/accessor-decorators.js
@@ -0,0 +1,189 @@
+// |jit-test| skip-if: !getBuildConfiguration("decorators")
+
+load(libdir + "asserts.js");
+
+let dec1Called = false;
+
+// This explicitly tests the case where undefined is returned.
+function dec(value, context) {
+ dec1Called = true;
+ // return undefined
+}
+
+function decorate_getter(value, context) {
+ return {
+ get: function() {
+ return 2 * value.get.call(this);
+ }
+ };
+}
+
+function decorate_setter(value, context) {
+ return {
+ set: function(x) {
+ return value.set.call(this, 2*x);
+ }
+ };
+}
+
+function decorate_initializer(value, context) {
+ return {
+ init: function(initialValue) {
+ return 2 * initialValue;
+ }
+ };
+}
+
+function checkDecoratorContext(kind, isPrivate, isStatic, name) {
+ return (value, context) => {
+ assertEq(typeof value, "object");
+ assertEq(typeof value.get, "function");
+ assertEq(typeof value.set, "function");
+ assertEq(context.kind, kind);
+ assertEq(typeof context.access, "object");
+ assertEq(context.private, isPrivate);
+ assertEq(context.static, isStatic);
+ assertEq(context.name, name);
+ if (isStatic) {
+ assertEq(typeof context.addInitializer, "undefined");
+ } else {
+ assertEq(typeof context.addInitializer, "function");
+ }
+ // return undefined
+ }
+}
+
+class C {
+ @dec accessor x = 1;
+ @decorate_getter accessor x2 = 1;
+ @decorate_setter @decorate_getter accessor x3 = 1;
+ @decorate_initializer accessor x4 = 1;
+ @decorate_initializer @decorate_initializer accessor x5 = 1;
+ @decorate_setter @decorate_getter @decorate_initializer accessor x6 = 1;
+ @checkDecoratorContext("accessor", true, false, "x8 accessor storage") accessor x8 = 1;
+ @checkDecoratorContext("accessor", true, true, "x9 accessor storage") static accessor x9 = 1;
+ @checkDecoratorContext("accessor", true, false, "#x10 accessor storage") accessor #x10 = 1;
+}
+
+let c = new C();
+assertEq(dec1Called, true);
+assertEq(c.x, 1);
+c.x = 2;
+assertEq(c.x, 2);
+assertEq(c.x2, 2);
+assertEq(c.x3, 2);
+c.x3 = 4;
+assertEq(c.x3, 16);
+assertEq(c.x4, 2);
+assertEq(c.x5, 4);
+assertEq(c.x6, 4);
+c.x6 = 4;
+assertEq(c.x6, 16);
+
+class D {
+ @decorate_initializer accessor #x = 1;
+ @decorate_getter accessor #x2 = 1;
+ @decorate_setter @decorate_getter accessor #x3 = 1;
+
+ getX() {
+ return this.#x;
+ }
+
+ setX(v) {
+ this.#x = v;
+ }
+
+ getX2() {
+ return this.#x2;
+ }
+
+ setX2(v) {
+ this.#x2 = v;
+ }
+
+ getX3() {
+ return this.#x3;
+ }
+
+ setX3(v) {
+ this.#x3 = v;
+ }
+}
+
+let d = new D();
+assertEq(d.getX(), 2);
+d.setX(4);
+assertEq(d.getX(), 4);
+assertEq(d.getX2(), 2);
+d.setX2(4);
+assertEq(d.getX2(), 8);
+assertEq(d.getX3(), 2);
+d.setX3(4);
+assertEq(d.getX3(), 16);
+
+class E {
+ @decorate_getter static accessor x = 1;
+}
+
+assertEq(E.x, 2);
+E.x = 2;
+assertEq(E.x, 4);
+
+class F {
+ @decorate_getter static accessor #x = 1;
+
+ getX() {
+ return F.#x;
+ }
+
+ setX(v) {
+ F.#x = v;
+ }
+}
+let f = new F();
+assertEq(f.getX(), 2);
+f.setX(4);
+assertEq(f.getX(), 8);
+
+assertThrowsInstanceOf(() => {
+ class G {
+ @(() => { return "hello!"; }) accessor x;
+ }
+}, TypeError), "Returning a value other than undefined or a callable throws.";
+
+assertThrowsInstanceOf(() => {
+ class G {
+ @(() => { return {get: "hello!"}; }) accessor x;
+ }
+}, TypeError), "Returning a value other than undefined or a callable throws.";
+
+assertThrowsInstanceOf(() => {
+ class G {
+ @(() => { return {set: "hello!"}; }) accessor x;
+ }
+}, TypeError), "Returning a value other than undefined or a callable throws.";
+
+assertThrowsInstanceOf(() => {
+ class G {
+ @(() => { return {init: "hello!"}; }) accessor x;
+ }
+}, TypeError), "Returning a value other than undefined or a callable throws.";
+
+const decoratorOrder = [];
+function makeOrderedDecorator(order) {
+ return function (value, context) {
+ decoratorOrder.push(order);
+ return value;
+ }
+}
+
+class H {
+ @makeOrderedDecorator(1) @makeOrderedDecorator(2) @makeOrderedDecorator(3)
+ accessor x = 1;
+}
+
+let h = new H();
+assertEq(decoratorOrder.length, 3);
+assertEq(decoratorOrder[0], 3);
+assertEq(decoratorOrder[1], 2);
+assertEq(decoratorOrder[2], 1);
diff --git a/js/src/jit-test/tests/decorators/accessors.js b/js/src/jit-test/tests/decorators/accessors.js
new file mode 100644
index 0000000000..660c6e79df
--- /dev/null
+++ b/js/src/jit-test/tests/decorators/accessors.js
@@ -0,0 +1,86 @@
+// |jit-test| skip-if: !getBuildConfiguration("decorators")
+
+load(libdir + "asserts.js");
+
+function assertAccessorDescriptor(object, name) {
+ var desc = Object.getOwnPropertyDescriptor(object, name);
+ assertEq(desc.configurable, true, "The value of `desc.configurable` is `true`");
+ assertEq(desc.enumerable, false, "The value of `desc.enumerable` is `false`");
+ assertEq(typeof desc.get, 'function', "`typeof desc.get` is `'function'`");
+ assertEq(typeof desc.set, 'function', "`typeof desc.set` is `'function'`");
+ assertEq(
+ 'prototype' in desc.get,
+ false,
+ "The result of `'prototype' in desc.get` is `false`"
+ );
+ assertEq(
+ 'prototype' in desc.set,
+ false,
+ "The result of `'prototype' in desc.set` is `false`"
+ );
+}
+
+class C {
+ accessor x;
+}
+
+assertAccessorDescriptor(C.prototype, 'x');
+
+let c = new C();
+assertEq(c.x, undefined, "The value of `c.x` is `undefined` after construction");
+c.x = 2;
+assertEq(c.x, 2, "The value of `c.x` is `2`, after executing `c.x = 2;`");
+
+class D {
+ accessor x = 1;
+}
+
+assertAccessorDescriptor(D.prototype, 'x');
+
+let d = new D();
+assertEq(d.x, 1, "The value of `d.x` is `1` after construction");
+d.x = 2;
+assertEq(d.x, 2, "The value of `d.x` is `2`, after executing `d.x = 2;`");
+
+class E {
+ accessor #x = 1;
+
+ getX() {
+ return this.#x;
+ }
+
+ setX(v) {
+ this.#x = v;
+ }
+}
+
+let e = new E();
+assertEq(e.getX(), 1, "The value of `e.x` is `1` after construction");
+e.setX(2);
+assertEq(e.getX(), 2, "The value of `e.x` is `2`, after executing `e.setX(2);`");
+
+class F {
+ static accessor x = 1;
+}
+
+assertEq(F.x, 1, "The value of `F.x` is `1` after construction");
+F.x = 2;
+assertEq(F.x, 2, "The value of `F.x` is `2`, after executing `F.x = 2;`");
+
+assertAccessorDescriptor(F, 'x');
+
+class G {
+ static accessor #x = 1;
+
+ getX() {
+ return G.#x;
+ }
+
+ setX(v) {
+ G.#x = v;
+ }
+}
+g = new G();
+assertEq(g.getX(), 1, "The value of `g.getX()` is `1` after construction");
+g.setX(2);
+assertEq(g.getX(), 2, "The value of `g.getX()` is `2`, after executing `g.setX(2)`");
diff --git a/js/src/jit-test/tests/decorators/addInitializer.js b/js/src/jit-test/tests/decorators/addInitializer.js
new file mode 100644
index 0000000000..334de8f721
--- /dev/null
+++ b/js/src/jit-test/tests/decorators/addInitializer.js
@@ -0,0 +1,39 @@
+// |jit-test| skip-if: !getBuildConfiguration("decorators")
+load(libdir + "asserts.js");
+
+let extraInitializerCalled = {};
+
+function checkDecoratorContext(kind, isPrivate, isStatic, name) {
+ return function (value, context) {
+ if (kind == "field") {
+ assertEq(value, undefined);
+ } else if (kind == "accessor") {
+ assertEq(typeof value, "object");
+ assertEq(typeof value.get, "function");
+ assertEq(typeof value.set, "function");
+ }
+ assertEq(context.kind, kind);
+ assertEq(typeof context.access, "object");
+ assertEq(context.private, isPrivate);
+ assertEq(context.static, isStatic);
+ assertEq(context.name, name);
+ assertEq(typeof context.addInitializer, "function");
+ context.addInitializer(() => {extraInitializerCalled[context.name] = true;});
+ // return undefined
+ }
+}
+
+class C {
+ @checkDecoratorContext("field", false, false, "x") x;
+ @checkDecoratorContext("accessor", true, false, "y accessor storage") accessor y;
+ @checkDecoratorContext("method", false, false, "f") f() {};
+ @checkDecoratorContext("method", false, false, 1) 1() {};
+ @checkDecoratorContext("method", false, false, 2) 2n() {};
+}
+
+let c = new C();
+assertEq(extraInitializerCalled["x"], true);
+assertEq(extraInitializerCalled["y accessor storage"], true);
+assertEq(extraInitializerCalled["f"], true);
+assertEq(extraInitializerCalled["1"], true);
+assertEq(extraInitializerCalled["2"], true);
diff --git a/js/src/jit-test/tests/decorators/class-decorators.js b/js/src/jit-test/tests/decorators/class-decorators.js
new file mode 100644
index 0000000000..f9b120ff45
--- /dev/null
+++ b/js/src/jit-test/tests/decorators/class-decorators.js
@@ -0,0 +1,101 @@
+// |jit-test| skip-if: !getBuildConfiguration("decorators")
+
+load(libdir + "asserts.js");
+
+let dec1Called = false;
+
+// This explicitly tests the case where undefined is returned.
+function dec1(value, context) {
+ dec1Called = true;
+ // returns undefined
+}
+
+function dec2(value, context) {
+ return class extends value {
+ constructor(...args) {
+ super(...args);
+ }
+
+ x2 = true;
+ }
+}
+
+function checkDecoratorContext(name) {
+ return function (value, context) {
+ assertEq(typeof value, "function");
+ assertEq(context.kind, "class");
+ assertEq(context.name, name);
+ assertEq(typeof context.addInitializer, "undefined");
+ // return undefined
+ }
+}
+
+@dec1 class C1 {};
+assertEq(dec1Called, true);
+
+@dec2 class C2 {
+ x1 = true;
+}
+
+let c2 = new C2();
+assertEq(c2.x1, true);
+assertEq(c2.x2, true);
+
+let c3 = new @dec2 class {
+ x1 = true;
+}
+
+assertEq(c3.x1, true);
+assertEq(c3.x2, true);
+
+@checkDecoratorContext("D") class D {}
+let d2 = new @checkDecoratorContext(undefined) class {};
+
+class E {
+ static #dec1(value, context) {
+ return class extends value {
+ constructor(...args) {
+ super(...args);
+ }
+
+ x2 = true;
+ }
+ }
+ static {
+ this.F = @E.#dec1 class {
+ x1 = true;
+ }
+ }
+}
+
+let f = new E.F();
+assertEq(f.x1, true);
+assertEq(f.x2, true);
+
+assertThrowsInstanceOf(() => {
+ @(() => { return "hello!"; }) class G {}
+}, TypeError), "Returning a value other than undefined or a callable throws.";
+
+assertThrowsInstanceOf(() => {
+ class G {
+ static #dec1() {}
+ static {
+ @G.#dec1 class G {}
+ }
+ }
+}, ReferenceError), "can't access lexical declaration 'G' before initialization";
+
+const decoratorOrder = [];
+function makeOrderedDecorator(order) {
+ return function (value, context) {
+ decoratorOrder.push(order);
+ return value;
+ }
+}
+
+@makeOrderedDecorator(1) @makeOrderedDecorator(2) @makeOrderedDecorator(3)
+class H {}
+assertEq(decoratorOrder.length, 3);
+assertEq(decoratorOrder[0], 3);
+assertEq(decoratorOrder[1], 2);
+assertEq(decoratorOrder[2], 1);
diff --git a/js/src/jit-test/tests/decorators/decorator-this.js b/js/src/jit-test/tests/decorators/decorator-this.js
new file mode 100644
index 0000000000..b545729ca4
--- /dev/null
+++ b/js/src/jit-test/tests/decorators/decorator-this.js
@@ -0,0 +1,36 @@
+// |jit-test| skip-if: !getBuildConfiguration("decorators")
+
+load(libdir + "asserts.js");
+
+let globalDecCalled = false;
+function globalDec(value, context) {
+ globalDecCalled = true;
+ assertEq(this, globalThis);
+}
+
+// Forward declare c to be able to check it inside of C when running decorators.
+let c;
+let classDecCalled = false;
+class C {
+ classDec(value, context) {
+ classDecCalled = true;
+ // At this point, `this` is an instance of C
+ assertEq(c, this);
+ return function(initialValue) {
+ // At this point, `this` is an instance of D
+ assertEq(this instanceof D, true);
+ return initialValue;
+ }
+ }
+}
+
+c = new C();
+
+class D {
+ @globalDec x1;
+ @c.classDec x2;
+}
+
+let d = new D();
+assertEq(globalDecCalled, true);
+assertEq(classDecCalled, true);
diff --git a/js/src/jit-test/tests/decorators/field-decorators.js b/js/src/jit-test/tests/decorators/field-decorators.js
new file mode 100644
index 0000000000..7b02f35ef9
--- /dev/null
+++ b/js/src/jit-test/tests/decorators/field-decorators.js
@@ -0,0 +1,104 @@
+// |jit-test| skip-if: !getBuildConfiguration("decorators")
+
+load(libdir + "asserts.js");
+
+let dec1Called = false;
+let dec2Called = false;
+
+// This explicitly tests the case where undefined is returned.
+function dec1(value, context) {
+ dec1Called = true;
+ // returns undefined
+}
+
+function dec2(value, context) {
+ dec2Called = true;
+ return (initialValue) => 2;
+}
+
+function dec3(value, context) {
+ return (initialValue) => initialValue*2;
+}
+
+function checkDecoratorContext(kind, isPrivate, isStatic, name) {
+ return (value, context) => {
+ assertEq(value, undefined);
+ assertEq(context.kind, kind);
+ assertEq(typeof context.access, "object");
+ assertEq(context.private, isPrivate);
+ assertEq(context.static, isStatic);
+ assertEq(context.name, name);
+ if (isStatic) {
+ assertEq(typeof context.addInitializer, "undefined");
+ } else {
+ assertEq(typeof context.addInitializer, "function");
+ }
+ }
+}
+
+class C {
+ @dec1 x;
+ @dec1 x2 = 1;
+ @checkDecoratorContext("field", false, false, "x3") x3;
+ @checkDecoratorContext("field", false, true, "x4") static x4;
+ @checkDecoratorContext("field", true, false, "#x5") #x5;
+ @dec2 x6;
+ @dec3 x7 = 1;
+ @dec2 @dec3 x8 = 1;
+ @dec2 static x9;
+ @dec2 #x10;
+ getX10 = () => {
+ return this.#x10;
+ };
+ @dec3 #x11 = 2;
+ getX11 = () => {
+ return this.#x11;
+ };
+ @dec1 42 = 1;
+ @dec2 [43];
+ @dec2 static #x12 = 1;
+ getX12() {
+ return C.#x12;
+ }
+}
+
+
+let c = new C();
+assertEq(dec1Called, true);
+assertEq(c.x2, 1);
+assertEq(dec2Called, true);
+assertEq(c.x6, 2);
+assertEq(c.x7, 2);
+assertEq(c.x8, 2);
+assertEq(C.x9, 2);
+assertEq(c.getX10(), 2);
+assertEq(c.getX11(), 4);
+assertEq(c[42], 1);
+assertEq(c[43], 2);
+assertEq(c.getX12(), 2);
+
+assertThrowsInstanceOf(() => {
+ class D {
+ @(() => { return "hello!"; }) f(x) { return x; }
+ }
+}, TypeError), "Returning a value other than undefined or a callable throws.";
+
+const decoratorOrder = [];
+function makeOrderedDecorator(order) {
+ return function (value, context) {
+ decoratorOrder.push(order);
+ return value;
+ }
+}
+
+class D {
+ @makeOrderedDecorator(1) @makeOrderedDecorator(2) @makeOrderedDecorator(3)
+ x = 1;
+}
+
+let d = new D();
+assertEq(decoratorOrder.length, 3);
+assertEq(decoratorOrder[0], 3);
+assertEq(decoratorOrder[1], 2);
+assertEq(decoratorOrder[2], 1);
+assertEq(d.x, 1);
diff --git a/js/src/jit-test/tests/decorators/getter-setter-decorators.js b/js/src/jit-test/tests/decorators/getter-setter-decorators.js
new file mode 100644
index 0000000000..e3c370f500
--- /dev/null
+++ b/js/src/jit-test/tests/decorators/getter-setter-decorators.js
@@ -0,0 +1,68 @@
+// |jit-test| skip-if: !getBuildConfiguration("decorators")
+
+load(libdir + "asserts.js");
+
+let dec1Called = false;
+
+// This explicitly tests the case where undefined is returned.
+function dec(value, context) {
+ dec1Called = true;
+ // return undefined
+}
+
+function decorate_getter(value, context) {
+ return function() {
+ return 2 * value.call(this);
+ }
+}
+
+function decorate_setter(value, context) {
+ return function(x) {
+ return value.call(this, 2*x);
+ }
+}
+
+function checkDecoratorContext(kind, isPrivate, isStatic, name) {
+ return (value, context) => {
+ assertEq(typeof value.call, "function");
+ assertEq(context.kind, kind);
+ assertEq(typeof context.access, "object");
+ assertEq(context.private, isPrivate);
+ assertEq(context.static, isStatic);
+ assertEq(context.name, name);
+ if (isStatic) {
+ assertEq(typeof context.addInitializer, "undefined");
+ } else {
+ assertEq(typeof context.addInitializer, "function");
+ }
+ }
+}
+
+class C {
+ setter5Value;
+
+ @dec get getter1() { return "unmodified"; }
+ @checkDecoratorContext("getter", false, false, "getter2") get getter2() { }
+ @checkDecoratorContext("getter", false, true, "getter3") static get getter3() { }
+ @checkDecoratorContext("getter", true, false, "#getter4") get #getter4() { }
+ @decorate_getter get getter5() { return 1; }
+
+ @dec set setter1(value) { }
+ @checkDecoratorContext("setter", false, false, "setter2") set setter2(value) { }
+ @checkDecoratorContext("setter", false, true, "setter3") static set setter3(value) { }
+ @checkDecoratorContext("setter", true, false, "#setter4") set #setter4(value) { }
+ @decorate_setter set setter5(value) { this.setter5Value = value; }
+}
+
+let c = new C();
+assertEq(dec1Called, true);
+assertEq(c.getter1, "unmodified");
+assertEq(c.getter5, 2);
+c.setter5 = 1;
+assertEq(c.setter5Value, 2);
+
+assertThrowsInstanceOf(() => {
+ class D {
+ @(() => { return "hello!"; }) get f() { return "unmodified"; }
+ }
+}, TypeError), "Returning a value other than undefined or a callable throws.";
diff --git a/js/src/jit-test/tests/decorators/method-decorators.js b/js/src/jit-test/tests/decorators/method-decorators.js
new file mode 100644
index 0000000000..59708aa70b
--- /dev/null
+++ b/js/src/jit-test/tests/decorators/method-decorators.js
@@ -0,0 +1,122 @@
+// |jit-test| skip-if: !getBuildConfiguration("decorators")
+
+load(libdir + "asserts.js");
+
+let dec1Called = false;
+
+// This explicitly tests the case where undefined is returned.
+function dec1(value, context) {
+ dec1Called = true;
+ // return undefined
+}
+
+function dec2(value, context) {
+ return (x) => "replaced: " + x;
+}
+
+function dec3(value, context) {
+ return (x) => "decorated: " + value.call(this, x);
+}
+
+let decorators = {
+ "dec4": (value, context) => {
+ return (x) => "decorated: " + x;
+ },
+ "more": {
+ "deeply": {
+ "nested": {
+ "dec5": (value, context) => {
+ return (x) => "decorated: " + x;
+ }
+ }
+ }
+ }
+};
+
+function dec6(arg) {
+ return (value, context) => {
+ return (x) => arg + " decorated: " + value.call(this, x);
+ }
+}
+
+function checkDecoratorContext(kind, isPrivate, isStatic, name) {
+ return (value, context) => {
+ assertEq(typeof value.call, "function");
+ assertEq(context.kind, kind);
+ assertEq(typeof context.access, "object");
+ assertEq(context.private, isPrivate);
+ assertEq(context.static, isStatic);
+ assertEq(context.name, name);
+ if (isStatic) {
+ assertEq(typeof context.addInitializer, "undefined");
+ } else {
+ assertEq(typeof context.addInitializer, "function");
+ }
+ }
+}
+
+class C {
+ @dec1 f1(x) { return "called with: " + x; }
+ @dec2 f2(x) { return "called with: " + x; }
+ @dec1 @dec2 f3(x) { return "called with: " + x; }
+ @dec1 @dec2 @dec3 f4(x) { return "called with: " + x; }
+ @decorators.dec4 f5(x) { return "called with: " + x; }
+ @decorators.more.deeply.nested.dec5 f6(x) { return "called with: " + x; }
+ @(() => { }) f7(x) { return "called with: " + x; }
+ @((value, context) => { return (x) => "decorated: " + x; }) f8(x) { return "called with: " + x; }
+ @dec6("hello!") f9(x) { return "called with: " + x; }
+
+ static dec7(value, context) { return function(x) { return "replaced: " + x; } }
+ static #dec8(value, context) { return function(x) { return "replaced: " + x; } }
+
+ static {
+ this.D = class {
+ @C.dec7 static f10(x) { return "called with: " + x; }
+ @C.#dec8 static f11(x) { return "called with: " + x; }
+ }
+ }
+
+ @checkDecoratorContext("method", false, false, "f12") f12(x) { return x; }
+ @checkDecoratorContext("method", false, true, "f13") static f13(x) { return x; }
+ @checkDecoratorContext("method", true, false, "#f14") #f14(x) { return x; }
+}
+
+let c = new C();
+assertEq(dec1Called, true);
+assertEq(c.f1("value"), "called with: value");
+assertEq(c.f2("value"), "replaced: value");
+assertEq(c.f3("value"), "replaced: value");
+assertEq(c.f4("value"), "replaced: value");
+assertEq(c.f5("value"), "decorated: value");
+assertEq(c.f6("value"), "decorated: value");
+assertEq(c.f7("value"), "called with: value");
+assertEq(c.f8("value"), "decorated: value");
+assertEq(c.f9("value"), "hello! decorated: called with: value");
+assertEq(C.D.f10("value"), "replaced: value");
+assertEq(C.D.f11("value"), "replaced: value");
+
+assertThrowsInstanceOf(() => {
+ class D {
+ @(() => { return "hello!"; }) f(x) { return x; }
+ }
+}, TypeError), "Returning a value other than undefined or a callable throws.";
+
+const decoratorOrder = [];
+function makeOrderedDecorator(order) {
+ return function (value, context) {
+ decoratorOrder.push(order);
+ return value;
+ }
+}
+
+class E {
+ @makeOrderedDecorator(1) @makeOrderedDecorator(2) @makeOrderedDecorator(3)
+ f(x) { return x; }
+}
+
+let e = new E();
+assertEq(e.f("value"), "value");
+assertEq(decoratorOrder.length, 3);
+assertEq(decoratorOrder[0], 3);
+assertEq(decoratorOrder[1], 2);
+assertEq(decoratorOrder[2], 1);
diff --git a/js/src/jit-test/tests/decorators/syntax.js b/js/src/jit-test/tests/decorators/syntax.js
new file mode 100644
index 0000000000..635e30bf7b
--- /dev/null
+++ b/js/src/jit-test/tests/decorators/syntax.js
@@ -0,0 +1,172 @@
+// |jit-test| skip-if: !getBuildConfiguration("decorators")
+
+load(libdir + "asserts.js");
+
+Reflect.parse("class c {@dec1 field = false;}");
+Reflect.parse("class c {@dec1 @dec2 @dec3 field = false;}");
+Reflect.parse("class c {@dec1 @dec2 @dec3('a') static field = false;}");
+Reflect.parse("class c {@dec1 method() {};}");
+Reflect.parse("class c {@dec1 @dec2 @dec3 method() {};}");
+Reflect.parse("class c {@dec1 @dec2 @dec3 static method() {};}");
+Reflect.parse("class c {@dec1 @dec2('a') @dec3 method(...args) {};}");
+Reflect.parse("class c {@((a, b, c) => {}) method(a, b) {};}");
+Reflect.parse("class c {@((a, b, c) => {}) @dec2('a', 'b') @dec3 method(a, b) {};}");
+Reflect.parse("@dec1 class c {}");
+Reflect.parse("@dec1 @dec2 @dec3 class c {}");
+Reflect.parse("@dec1('a') @(() => {}) @dec3 class c {}");
+Reflect.parse("@dec1('a') @(() => {}) @dec3 class c {@((a, b, c) => {}) @dec2('a', 'b') @dec3 method(a, b) {};}");
+Reflect.parse("x = @dec class { #x }");
+Reflect.parse("x = (class A { }, @dec class { })");
+Reflect.parse("@dec1 class A extends @dec2 class B extends @dec3 class {} {} {}");
+Reflect.parse("class c {@dec1.dec2.dec3 method() {};}");
+Reflect.parse("class c {@dec1 @dec2.dec3.dec4 @dec5 method() {};}");
+Reflect.parse("class c {@dec1('a') @dec2.dec3.dec4 @dec5 method() {};}");
+Reflect.parse("class c {@dec1('a') @dec2.dec3.dec4('b', 'c') @dec5 method() {};}");
+Reflect.parse("class c {@dec1('a') @dec2.dec3.dec4('b', 'c') @dec5.dec6 method() {};}");
+Reflect.parse("@dec1.dec2 class c {}");
+Reflect.parse("@dec1.dec2 @dec3.dec4 @dec5 class c {}");
+Reflect.parse("@dec1.dec2('a') @(() => {}) @dec4 class c {}");
+Reflect.parse("@dec1.dec2('a') @(() => {}) @dec4 class c {@((a, b, c) => {}) @dec5.dec6('a', 'b') @dec7 method(a, b) {};}");
+Reflect.parse("class c {accessor field = false;}");
+Reflect.parse("class c {static accessor field = false;}");
+Reflect.parse("class c {@dec1 @dec2 accessor field = false;}");
+Reflect.parse("class c {@dec1 @dec2 @dec3 static accessor field = false;}");
+Reflect.parse("let accessor = false;");
+Reflect.parse("class accessor {accessor = false;}");
+Reflect.parse("class c {accessor = false;}");
+Reflect.parse("class c {accessor\n= false;}");
+Reflect.parse("class c {accessor\nfield = false;}");
+Reflect.parse("class C {accessor\n foo() {}}");
+Reflect.parse("class c {accessor; }");
+Reflect.parse("class c {accessor\nset field(a) {}}");
+Reflect.parse("class c {accessor\nasync field(a) {}}");
+Reflect.parse("class c {accessor\n* field(a) {}}");
+Reflect.parse("{accessor, field(a)}");
+Reflect.parse("class c {static accessor x = 1;}");
+Reflect.parse("class c {accessor #y = 2;}");
+Reflect.parse("class c {static dec1(){}; static {@c.dec1 class d{@c.dec1 field}}}");
+Reflect.parse("class c {static #dec1(){}; static {@c.#dec1 class d{@c.#dec1 field}}}");
+Reflect.parse("class c {@as field = false;}");
+Reflect.parse("class c {@accessor field = false;}");
+Reflect.parse("class c {@assert field = false;}");
+Reflect.parse("class c {@async field = false;}");
+Reflect.parse("class c {@await field = false;}");
+Reflect.parse("class c {@each field = false;}");
+Reflect.parse("class c {@from field = false;}");
+Reflect.parse("class c {@get field = false;}");
+Reflect.parse("class c {@meta field = false;}");
+Reflect.parse("class c {@of field = false;}");
+Reflect.parse("class c {@set field = false;}");
+Reflect.parse("class c {@target field = false;}");
+
+assertThrowsInstanceOf(() => Reflect.parse("class c {@ method() {};}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {@((a, b, c => {}) method(a, b) {};}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {@dec1 static @dec2 method(a, b) {};}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("@dec1 let x = 1"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("@dec1 f(a) {}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("@dec1 () => {}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("@class class { x; }"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("@for class { x; }"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {@dec1. method() {};}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("@dec1. class c {method() {};}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("@dec1.(a) class c {method() {};}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {accessor method() {};}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {accessor @dec1 field = false;}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {accessor, }"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("({ accessor field: 10 })"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("({ accessor foo });"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("({ accessor foo = 10 } = {});"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {async accessor foo() {}}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {accessor set field(a) {}}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {accessor *field(a) {}}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {accessor *field(a) {}}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {@dec[0] x}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {@new dec() x}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {@super x}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {@super() x}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {@super.dec() x}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {@import \"decorator\" x}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {@dec`template` x}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {@import.meta.url x}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {@new.target x}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {@obj.first?.second x}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {@class x}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {@function() x}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {@{} x}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {@[] x}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {@\"string\" x}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {@`string` x}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {@/[a-z]+/ x}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {@true x}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {@1n x}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {@this x}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {@null x}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {@... x}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {@let x}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {@static x}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {@yield x}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {@if x}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {@enum x}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {@implements x}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {@foo().bar.prop x}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {@foo.bar().prop x}"), SyntaxError);
+assertThrowsInstanceOf(() => Reflect.parse("class c {@foo.bar().prop() x}"), SyntaxError);
+
+// assert we have the right syntax tree
+const syntax = Reflect.parse("class c {}");
+assertEq(syntax.type, "Program");
+assertEq(syntax.body.length, 1);
+assertEq(syntax.body[0].type, "ClassStatement");
+assertEq(syntax.body[0].id.type, "Identifier");
+assertEq(syntax.body[0].id.name, "c");
+assertEq(syntax.body[0].decorators, null);
+
+// assert decrorators on a class
+const syntax2 = Reflect.parse("@dec @dec2 class c {}");
+assertEq(syntax2.type, "Program");
+assertEq(syntax2.body.length, 1);
+assertEq(syntax2.body[0].decorators.type, "SequenceExpression");
+assertEq(syntax2.body[0].decorators.expressions.length, 2);
+assertEq(syntax2.body[0].decorators.expressions[0].type, "Identifier");
+assertEq(syntax2.body[0].decorators.expressions[0].name, "dec");
+assertEq(syntax2.body[0].decorators.expressions[1].type, "Identifier");
+assertEq(syntax2.body[0].decorators.expressions[1].name, "dec2");
+
+// assert decorators on class fields
+const syntax3 = Reflect.parse("class c {@dec1 @dec2 field = false;}");
+assertEq(syntax3.type, "Program");
+assertEq(syntax3.body.length, 1);
+assertEq(syntax3.body[0].decorators, null);
+assertEq(syntax3.body[0].body[0].type, "ClassField");
+assertEq(syntax3.body[0].body[0].decorators.type, "SequenceExpression");
+assertEq(syntax3.body[0].body[0].decorators.expressions.length, 2);
+assertEq(syntax3.body[0].body[0].decorators.expressions[0].type, "Identifier");
+assertEq(syntax3.body[0].body[0].decorators.expressions[0].name, "dec1");
+assertEq(syntax3.body[0].body[0].decorators.expressions[1].type, "Identifier");
+assertEq(syntax3.body[0].body[0].decorators.expressions[1].name, "dec2");
+
+// assert decorators on accessors
+const syntax4 = Reflect.parse("class c {@dec1 @dec2 accessor field = false;}");
+assertEq(syntax3.type, "Program");
+assertEq(syntax3.body.length, 1);
+assertEq(syntax3.body[0].decorators, null);
+assertEq(syntax3.body[0].body[0].type, "ClassField");
+assertEq(syntax3.body[0].body[0].decorators.type, "SequenceExpression");
+assertEq(syntax3.body[0].body[0].decorators.expressions.length, 2);
+assertEq(syntax3.body[0].body[0].decorators.expressions[0].type, "Identifier");
+assertEq(syntax3.body[0].body[0].decorators.expressions[0].name, "dec1");
+assertEq(syntax3.body[0].body[0].decorators.expressions[1].type, "Identifier");
+assertEq(syntax3.body[0].body[0].decorators.expressions[1].name, "dec2");
+
+// assert decorators on methods
+const syntax5 = Reflect.parse("class c {@dec1 @dec2 method() {};}");
+assertEq(syntax5.type, "Program");
+assertEq(syntax5.body.length, 1);
+assertEq(syntax5.body[0].decorators, null);
+assertEq(syntax5.body[0].body[0].type, "ClassMethod");
+assertEq(syntax5.body[0].body[0].decorators.type, "SequenceExpression");
+assertEq(syntax5.body[0].body[0].decorators.expressions.length, 2);
+assertEq(syntax5.body[0].body[0].decorators.expressions[0].type, "Identifier");
+assertEq(syntax5.body[0].body[0].decorators.expressions[0].name, "dec1");
+assertEq(syntax5.body[0].body[0].decorators.expressions[1].type, "Identifier");
+assertEq(syntax5.body[0].body[0].decorators.expressions[1].name, "dec2");