| //------------------------------------------------------------------------------------------------------- |
| // Copyright (C) Microsoft. All rights reserved. |
| // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. |
| //------------------------------------------------------------------------------------------------------- |
| |
| WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js"); |
| |
| function verifyClassMember(obj, name, expectedReturnValue, isGet, isSet, isGenerator) { |
| let p = Object.getOwnPropertyDescriptor(obj, name); |
| let strMethodSignature = `obj[${name}](${isGet},${isSet},${isGenerator})`; |
| let fn; |
| |
| if (isGet) { |
| fn = p.get; |
| |
| assert.areEqual('function', typeof fn, `${strMethodSignature}: Get method has 'get' property set in descriptor`); |
| assert.areEqual(expectedReturnValue, obj[name], `${strMethodSignature}: Invoking class get method returns correct value`); |
| assert.areEqual(expectedReturnValue, fn(), `${strMethodSignature}: Calling class get method function directly returns correct value`); |
| |
| assert.isFalse('prototype' in fn, `${strMethodSignature}: Class method get does not have 'prototype' property`); |
| } else if (isSet) { |
| fn = p.set; |
| |
| assert.areEqual('function', typeof fn, `${strMethodSignature}: Set method has 'set' property set in descriptor`); |
| assert.areEqual(undefined, obj[name], `${strMethodSignature}: Invoking class set method returns undefined`); |
| assert.areEqual(expectedReturnValue, fn(), `${strMethodSignature}: Calling class set method function directly returns correct value`); |
| |
| assert.isFalse('prototype' in fn, `${strMethodSignature}: Class method set does not have 'prototype' property`); |
| |
| |
| } else if (isGenerator) { |
| fn = obj[name]; |
| |
| assert.areEqual('function', typeof fn, `${strMethodSignature}: Class method generator function has correct type`); |
| |
| let s; |
| for (s of fn()) {} |
| assert.areEqual(expectedReturnValue, s, `${strMethodSignature}: Calling class method generator returns correct value`); |
| |
| assert.isTrue(p.writable, `${strMethodSignature}: Class method generator functions are writable`); |
| |
| assert.isTrue('prototype' in fn, `${strMethodSignature}: Class method generator does have 'prototype' property`); |
| } else { |
| fn = obj[name] |
| |
| assert.areEqual('function', typeof fn, `${strMethodSignature}: Class method has correct type`); |
| assert.areEqual(expectedReturnValue, fn(), `${strMethodSignature}: Calling class method returns correct value`); |
| |
| // get/set property descriptors do not have writable properties |
| assert.isTrue(p.writable, `${strMethodSignature}: Class method functions are writable`); |
| |
| assert.isFalse('prototype' in fn, `${strMethodSignature}: Class method does not have 'prototype' property`); |
| } |
| |
| assert.isFalse(p.enumerable, `${strMethodSignature}: Class methods are not enumerable`); |
| assert.isTrue(p.configurable, `${strMethodSignature}: Class methods are configurable`); |
| |
| assert.isFalse(fn.hasOwnProperty('arguments'), `${strMethodSignature}: Class methods do not have an 'arguments' property`); |
| assert.isFalse(fn.hasOwnProperty('caller'), `${strMethodSignature}: Class methods do not have an 'caller' property`); |
| } |
| |
| var tests = [ |
| { |
| name: "Class requires an extends expression if the extends keyword is used", |
| body: function () { |
| assert.throws(function () { eval("class E extends { }") }, SyntaxError); |
| } |
| }, |
| { |
| name: "Class declarations require a name", |
| body: function () { |
| assert.throws(function () { eval("class { }") }, SyntaxError); |
| } |
| }, |
| { |
| name: "Class methods may not have an octal name", |
| body: function () { |
| assert.throws(function () { eval("class E0 { 0123() {} }") }, SyntaxError, "0123"); |
| assert.throws(function () { eval("class E1 { 0123.1() {} }") }, SyntaxError, "0123.1"); |
| } |
| }, |
| { |
| name: "Class prototypes must be non-writable", |
| body: function () { |
| var d = Object.getOwnPropertyDescriptor(class { }, "prototype"); |
| assert.isFalse(d.writable); |
| } |
| }, |
| { |
| name: "Class static methods may not be named 'prototype'", |
| body: function () { |
| assert.throws(function () { eval("class E0 { static prototype() {} }") }, SyntaxError, "static prototype"); |
| assert.throws(function () { eval("class E1 { static get prototype() {} }") }, SyntaxError, "static get prototype"); |
| assert.throws(function () { eval("class E2 { static set prototype(x) {} }") }, SyntaxError, "static set prototype"); |
| } |
| }, |
| { |
| name: "Class constructor method can only be a normal method - not getter, setter, or generator", |
| body: function () { |
| assert.throws(function () { eval("class E { * constructor() {} }") }, SyntaxError, "Class constructor may not be a generator"); |
| assert.throws(function () { eval("class E0 { get constructor() {} }") }, SyntaxError, "get constructor"); |
| assert.throws(function () { eval("class E1 { set constructor(x) {} }") }, SyntaxError, "set constructor"); |
| } |
| }, |
| { |
| name: "Class method names can be duplicated; last one lexically always win", |
| body: function () { |
| assert.throws(function () { eval("class E0 { constructor() {} constructor() {} }") }, SyntaxError, "Duplicated constructor"); |
| |
| // Valid |
| class A { foo() {} foo() {} } |
| class B { get foo() {} get foo() {} } |
| class C { set foo(x) {} set foo(x) {} } |
| class D { get foo() {} set foo(x) {} } |
| class E { static foo() {} static foo() {} } |
| class F { static get foo() {} static get foo() {} } |
| class G { static set foo(x) {} static set foo(x) {} } |
| class H { static get foo() {} static set foo(x) {} } |
| class I { foo() {} get foo() {} set foo(x) {}} |
| class J { static get foo() {} static set foo(x) {} get foo() {} set foo(x) {} } |
| class K { static foo() {} static get foo() {} static set foo(x) {}} |
| |
| class L { static foo() {} foo() {} } |
| class M { static foo() {} get foo() {} set foo(x) {}} |
| class N { foo() {} static get foo() {} static set foo(x) {}} |
| } |
| }, |
| { |
| name: "Class extends expressions must be (null || an object that is a constructor with a prototype that is (null || an object))", |
| body: function () { |
| class BaseClass {} |
| assert.isTrue(Object.getPrototypeOf(BaseClass.prototype) === Object.prototype, "Object.getPrototypeOf(BaseClass.prototype) === Object.prototype") |
| assert.isTrue(Object.getPrototypeOf(BaseClass.prototype.constructor) === Function.prototype, "Object.getPrototypeOf(BaseClass.prototype.constructor) === Function.prototype") |
| |
| class ExtendsNull extends null { } |
| assert.isTrue(Object.getPrototypeOf(ExtendsNull.prototype) === null, "Object.getPrototypeOf(ExtendsNull.prototype) === null") |
| assert.isTrue(Object.getPrototypeOf(ExtendsNull.prototype.constructor) === Function.prototype, "Object.getPrototypeOf(ExtendsNull.prototype.constructor) === Function.prototype") |
| |
| function NullPrototype () {} |
| NullPrototype.prototype = null; |
| class ExtendsNullPrototype extends NullPrototype {} |
| assert.isTrue(Object.getPrototypeOf(ExtendsNullPrototype.prototype) === null, "Object.getPrototypeOf(ExtendsNullPrototype.prototype) === null") |
| assert.isTrue(Object.getPrototypeOf(ExtendsNullPrototype.prototype.constructor) === NullPrototype, "Object.getPrototypeOf(ExtendsNullPrototype.prototype.constructor) === NullPrototype") |
| |
| class ExtendsObject extends Object {} |
| assert.isTrue(Object.getPrototypeOf(ExtendsObject.prototype) === Object.prototype, "Object.getPrototypeOf(ExtendsObject.prototype) === Object.prototype") |
| assert.isTrue(Object.getPrototypeOf(ExtendsObject.prototype.constructor) === Object, "Object.getPrototypeOf(ExtendsObject.prototype.constructor) === Object") |
| |
| function Func () {} |
| class ExtendsFunc extends Func {} |
| assert.isTrue(Object.getPrototypeOf(ExtendsFunc.prototype) === Func.prototype, "Object.getPrototypeOf(ExtendsFunc.prototype) === Func.prototype") |
| assert.isTrue(Object.getPrototypeOf(ExtendsFunc.prototype.constructor) === Func, "Object.getPrototypeOf(ExtendsFunc.prototype.constructor) === Func") |
| |
| |
| assert.throws(function () { class A extends 0 { } }, TypeError, "Integer extends"); |
| assert.throws(function () { class A extends "test" { } }, TypeError, "String extends"); |
| assert.throws(function () { class A extends {} { } }, TypeError, "Object literal extends"); |
| assert.throws(function () { class A extends undefined { } }, TypeError, "Undefined extends"); |
| assert.throws( |
| function () { |
| function Foo() {} |
| Foo.prototype = 0; |
| class A extends Foo { } |
| }, TypeError, "Integer prototype"); |
| assert.throws( |
| function () { |
| function Foo() {} |
| Foo.prototype = "test"; |
| class A extends Foo { } |
| }, TypeError, "String prototype"); |
| assert.throws( |
| function () { |
| function Foo() {} |
| Foo.prototype = undefined; |
| class A extends Foo { } |
| }, TypeError, "Undefined prototype"); |
| |
| assert.doesNotThrow(function () { eval("class Foo extends new Proxy(class Bar {},{}){}"); }); |
| } |
| }, |
| { |
| name: "Class basic sanity tests", |
| body: function () { |
| var m; |
| function p(x) { |
| m = x; |
| } |
| |
| class Empty { } |
| class EmptySemi { ; } |
| class OnlyCtor { constructor() { p('ctor') } } |
| class OnlyMethod { method() { p('method') } } |
| class OnlyStaticMethod { static method() { p('smethod') } } |
| class OnlyGetter { get getter() { p('getter') } } |
| class OnlyStaticGetter { static get getter() { p('sgetter') } } |
| class OnlySetter { set setter(x) { p('setter ' + x) } } |
| class OnlyStaticSetter { static set setter(x) { p('ssetter ' + x) } } |
| |
| let empty = new Empty(); |
| let emptySemi = new EmptySemi(); |
| |
| let onlyCtor = new OnlyCtor(); |
| assert.areEqual('ctor', m, "constructing OnlyCtor class calls the constructor method"); |
| |
| let onlyMethod = new OnlyMethod(); |
| let onlyStaticMethod = new OnlyStaticMethod(); |
| let onlyGetter = new OnlyGetter(); |
| let onlyStaticGetter = new OnlyStaticGetter(); |
| let onlySetter = new OnlySetter(); |
| let onlyStaticSetter = new OnlyStaticSetter(); |
| |
| onlyMethod.method(); |
| assert.areEqual('method', m, "method calls function correctly"); |
| |
| OnlyStaticMethod.method(); |
| assert.areEqual('smethod', m, "static method calls function correctly"); |
| |
| onlyGetter.getter; |
| assert.areEqual('getter', m, "getter calls function correctly"); |
| |
| OnlyStaticGetter.getter; |
| assert.areEqual('sgetter', m, "static getter calls function correctly"); |
| |
| onlySetter.setter = null; |
| assert.areEqual('setter null', m, "setter calls function correctly"); |
| |
| OnlyStaticSetter.setter = null; |
| assert.areEqual('ssetter null', m, "static setter calls function correctly"); |
| |
| |
| class InheritMethod extends OnlyMethod { method2() { p('sub method') } } |
| class OverrideMethod extends OnlyMethod { method() { p('override method') } } |
| |
| let inheritMethod = new InheritMethod(); |
| let overrideMethod = new OverrideMethod(); |
| |
| inheritMethod.method(); |
| assert.areEqual('method', m, "derived class inherits base class method"); |
| |
| inheritMethod.method2(); |
| assert.areEqual('sub method', m, "derived class adds new method correctly"); |
| |
| overrideMethod.method(); |
| assert.areEqual('override method', m, "derived class overrides method correctly"); |
| |
| |
| let OnlyMethodExpr = class OnlyMethodExpr { method() { p('class expr method') } } |
| let OnlyMethodExprNameless = class { method() { p('class expr no name method') } } |
| |
| let onlyMethodExpr = new OnlyMethodExpr(); |
| let onlyMethodExprNameless = new OnlyMethodExprNameless(); |
| |
| onlyMethodExpr.method(); |
| assert.areEqual('class expr method', m, "method call on class expression works correctly"); |
| |
| onlyMethodExprNameless.method(); |
| assert.areEqual('class expr no name method', m, "method call on class expression with no name works correctly"); |
| |
| |
| class InternalNameUse { static method() { p(InternalNameUse.method.toString()) } } |
| let InternalNameUseExpr_ = class InternalNameUseExpr { static method() { p(InternalNameUseExpr.method.toString()) } } |
| |
| InternalNameUse.method(); |
| assert.areEqual('method() { p(InternalNameUse.method.toString()) }', m, "Use of class declaration's name inside method works correctly"); |
| |
| InternalNameUseExpr_.method(); |
| assert.areEqual('method() { p(InternalNameUseExpr.method.toString()) }', m, "Use of class expression's name inside method works correctly"); |
| } |
| }, |
| { |
| name: "Class basic sanity tests in closures", |
| body: function () { |
| var m; |
| function p(x) { |
| m = x; |
| } |
| |
| function f1() { |
| class Empty { } |
| class EmptySemi { ; } |
| class OnlyCtor { constructor() { p('ctor') } } |
| class OnlyMethod { method() { p('method') } } |
| class OnlyStaticMethod { static method() { p('smethod') } } |
| class OnlyGetter { get getter() { p('getter') } } |
| class OnlyStaticGetter { static get getter() { p('sgetter') } } |
| class OnlySetter { set setter(x) { p('setter ' + x) } } |
| class OnlyStaticSetter { static set setter(x) { p('ssetter ' + x) } } |
| class OnlyComputedMethod { ["cmethod"]() { p('cmethod') } } |
| class OnlyStaticComputedMethod { static ["cmethod"]() { p('scmethod') } } |
| class OnlyComputedGetter { get ["cgetter"]() { p('cgetter') } } |
| class OnlyStaticComputedGetter { static get ["cgetter"]() { p('scgetter') } } |
| class OnlyComputedSetter { set ["csetter"](x) { p('csetter ' + x) } } |
| class OnlyStaticComputedSetter { static set ["csetter"](x) { p('scsetter ' + x) } } |
| |
| function f2() { |
| let empty = new Empty(); |
| let emptySemi = new EmptySemi(); |
| |
| let onlyCtor = new OnlyCtor(); |
| assert.areEqual('ctor', m, "constructing OnlyCtor class calls the constructor method"); |
| |
| let onlyMethod = new OnlyMethod(); |
| let onlyStaticMethod = new OnlyStaticMethod(); |
| let onlyGetter = new OnlyGetter(); |
| let onlyStaticGetter = new OnlyStaticGetter(); |
| let onlySetter = new OnlySetter(); |
| let onlyStaticSetter = new OnlyStaticSetter(); |
| let onlyComputedMethod = new OnlyComputedMethod(); |
| let onlyComputedGetter = new OnlyComputedGetter(); |
| let onlyComputedSetter = new OnlyComputedSetter(); |
| |
| onlyMethod.method(); |
| assert.areEqual('method', m, "method calls function correctly"); |
| |
| OnlyStaticMethod.method(); |
| assert.areEqual('smethod', m, "static method calls function correctly"); |
| |
| onlyGetter.getter; |
| assert.areEqual('getter', m, "getter calls function correctly"); |
| |
| OnlyStaticGetter.getter; |
| assert.areEqual('sgetter', m, "static getter calls function correctly"); |
| |
| onlySetter.setter = null; |
| assert.areEqual('setter null', m, "setter calls function correctly"); |
| |
| OnlyStaticSetter.setter = null; |
| assert.areEqual('ssetter null', m, "static setter calls function correctly"); |
| |
| onlyComputedMethod.cmethod() |
| assert.areEqual('cmethod', m, "computed name method calls function correctly"); |
| |
| OnlyStaticComputedMethod.cmethod() |
| assert.areEqual('scmethod', m, "static computed name method calls function correctly"); |
| |
| onlyComputedGetter.cgetter; |
| assert.areEqual('cgetter', m, "computed name getter calls function correctly"); |
| |
| OnlyStaticComputedGetter.cgetter; |
| assert.areEqual('scgetter', m, "static computed name getter calls function correctly"); |
| |
| onlyComputedSetter.csetter = null; |
| assert.areEqual('csetter null', m, "computed name setter calls function correctly"); |
| |
| OnlyStaticComputedSetter.csetter = null; |
| assert.areEqual('scsetter null', m, "static computed name setter calls function correctly"); |
| } |
| |
| f2(); |
| } |
| f1(); |
| |
| function f3() { |
| class OnlyMethod { method() { p('method') } } |
| class InheritMethod extends OnlyMethod { method2() { p('sub method') } } |
| class OverrideMethod extends OnlyMethod { method() { p('override method') } } |
| |
| function f4() { |
| let inheritMethod = new InheritMethod() |
| let overrideMethod = new OverrideMethod() |
| |
| inheritMethod.method(); |
| assert.areEqual('method', m, "derived class inherits base class method"); |
| |
| inheritMethod.method2(); |
| assert.areEqual('sub method', m, "derived class adds new method correctly"); |
| |
| overrideMethod.method(); |
| assert.areEqual('override method', m, "derived class overrides method correctly"); |
| } |
| |
| f4(); |
| } |
| f3(); |
| |
| function f5() { |
| let OnlyMethodExpr = class OnlyMethodExpr { method() { p('class expr method') } } |
| let OnlyMethodExprNameless = class { method() { p('class expr no name method') } } |
| |
| function f6() { |
| let onlyMethodExpr = new OnlyMethodExpr(); |
| let onlyMethodExprNameless = new OnlyMethodExprNameless(); |
| |
| onlyMethodExpr.method(); |
| assert.areEqual('class expr method', m, "method call on class expression works correctly"); |
| |
| onlyMethodExprNameless.method(); |
| assert.areEqual('class expr no name method', m, "method call on class expression with no name works correctly"); |
| } |
| |
| f6() |
| } |
| f5() |
| |
| function f7() { |
| class InternalNameUse { static method() { p(InternalNameUse.method.toString()) } } |
| let InternalNameUseExpr_ = class InternalNameUseExpr { static method() { p(InternalNameUseExpr.method.toString()) } } |
| |
| function f8() { |
| InternalNameUse.method(); |
| assert.areEqual('method() { p(InternalNameUse.method.toString()) }', m, "Use of class declaration's name inside method works correctly"); |
| |
| InternalNameUseExpr_.method(); |
| assert.areEqual('method() { p(InternalNameUseExpr.method.toString()) }', m, "Use of class expression's name inside method works correctly"); |
| } |
| |
| f8() |
| } |
| f7() |
| } |
| }, |
| { |
| name: "Invalid uses of super", |
| body: function () { |
| class A { |
| constructor() { p('constructor A'); } |
| method() { p('method A'); } |
| } |
| |
| // Test valid postfix operators in the wrong context |
| assert.throws(function () { eval("super();") }, ReferenceError, "Invalid use of super"); |
| assert.throws(function () { eval("super[1];") }, ReferenceError, "Invalid use of super"); |
| assert.throws(function () { eval("super.method();") }, ReferenceError, "Invalid use of super"); |
| |
| // Syntax Error for base class constructor with direct super call |
| assert.throws(function () { eval("class A { constructor() { super(); } }") }, SyntaxError, "Base class constructor cannot call super"); |
| } |
| }, |
| { |
| name: "Basic uses of super", |
| body: function () { |
| var ctorCalls = []; |
| |
| class A { |
| constructor() { this._initialized = true; ctorCalls.push('A'); } |
| method() { return 'method A'; } |
| set initialized(v) { this._initialized = v; } |
| get initialized() { return this._initialized; } |
| } |
| |
| class B extends A { |
| constructor() { |
| ctorCalls.push('B pre-super'); |
| super(); |
| ctorCalls.push('B post-super'); |
| } |
| superMethod() { return super.method() } |
| superMethodIndex() { return super['method'](); } |
| getAprop() { return super.initialized; } |
| setAprop(value) { super.initialized = value; } |
| getAIndex() { return super['initialized']; } |
| setAIndex(value) { super['initialized'] = value; } |
| lambdaIndex() { |
| var mysuper = x => super[x](); |
| return mysuper('method'); |
| } |
| } |
| |
| let classA = new A(); |
| assert.areEqual(1, ctorCalls.length, "new A calls A's constructor once"); |
| assert.areEqual('A', ctorCalls[0], "new A calls A's constructor"); |
| ctorCalls = []; |
| |
| let classB = new B(); |
| assert.areEqual(3, ctorCalls.length, "new B calls B and A constructors once each"); |
| assert.areEqual('B pre-super', ctorCalls[0], "new B calls B's constructor first"); |
| assert.areEqual('A', ctorCalls[1], "super within B's constructor calls A's constructor"); |
| assert.areEqual('B post-super', ctorCalls[2], "A's constructor returns to B's constructor after super call"); |
| |
| |
| // Sanity checks |
| assert.isTrue(classA.method() === 'method A', "classA.method() === 'method A'"); |
| assert.isTrue(classA.initialized === true, "classA.initialized === true"); |
| |
| // Super checks |
| assert.isTrue(classB.initialized === true, "classB.initialized === true"); |
| assert.isTrue(classB.superMethod() === 'method A', "classB.superMethod() === 'method A'"); |
| assert.isTrue(classB.initialized === true, "classB.initialized === true"); |
| classB.setAprop(123); |
| assert.isTrue(classB.getAprop() === 123, "classB.getAprop() === 123"); |
| assert.isTrue(classB.getAIndex() === 123, "classB.getAIndex() === 123"); |
| classB.setAIndex(456); |
| assert.isTrue(classB.getAprop() === 456, "classB.getAprop() === 456"); |
| assert.isTrue(classB.getAIndex() === 456, "classB.getAIndex() === 456"); |
| |
| assert.isTrue(classB.lambdaIndex() === 'method A', "classB.lambdaIndex() === 'method A'"); |
| } |
| }, |
| { |
| name: "Super used outside the class declaration function", |
| body: function () { |
| class A1 |
| { |
| method() { return 3; } |
| }; |
| |
| class A2 |
| { |
| method() { return 2; } |
| } |
| |
| function GetClassB(Asomething) |
| { |
| class B extends (Asomething) |
| { |
| method() { return 4; } |
| supermethod() { return super.method(); } |
| }; |
| return B; |
| } |
| |
| let classB1 = GetClassB(A1); |
| let classB2 = GetClassB(A2); |
| let b1 = new classB1(); |
| let b2 = new classB2(); |
| |
| assert.isTrue(b1.method() === 4, "b1.method() === 4)"); |
| assert.isTrue(b1.supermethod() === 3, "b1.supermethod() === 3"); |
| |
| assert.isTrue(b2.method() === 4, "b2.method() === 4"); |
| assert.isTrue(b2.supermethod() === 2, "b2.supermethod() === 2"); |
| } |
| }, |
| { |
| name: "Super adapts to __proto__ changes", |
| body: function () { |
| class A1 { |
| method() { return "A1"; } |
| static staticMethod() { return "static A1"; } |
| } |
| class A2 { |
| method() { return "A2"; } |
| static staticMethod() { return "static A2"; } |
| } |
| class A3 { |
| method() { return "A3"; } |
| static staticMethod() { return "static A3"; } |
| } |
| |
| class B extends A1 { |
| method() { return super.method(); } |
| static staticMethod() { return super.staticMethod(); } |
| } |
| |
| assert.areEqual(B.__proto__, A1); |
| assert.areEqual(B.prototype.__proto__, A1.prototype); |
| |
| let instanceB1 = new B(); |
| let instanceB2 = new B(); |
| assert.areEqual("A1", instanceB1.method()); |
| assert.areEqual("static A1", B.staticMethod()); |
| assert.areEqual(instanceB1.method(), instanceB2.method()); |
| |
| // Change the 'static' part of B |
| B.__proto__ = A2; |
| |
| assert.areEqual(B.__proto__, A2); |
| assert.areEqual(B.prototype.__proto__, A1.prototype); |
| assert.areEqual("A1", instanceB1.method(), "Instance methods should not be affected by B.__proto__ change"); |
| assert.areEqual("static A2", B.staticMethod(), "Static method should have changed after B.__proto__ change"); |
| assert.areEqual(instanceB1.method(), instanceB2.method(), "All instances should not have been affected by B.__proto__ change"); |
| |
| // Change the 'dynamic' part of B |
| B.prototype.__proto__ = A3.prototype; |
| |
| assert.areEqual(B.__proto__, A2); |
| assert.areEqual(B.prototype.__proto__, A3.prototype); |
| assert.areEqual("A3", instanceB1.method(), "Instance methods should be affected after B.prototype.__proto__ change"); |
| assert.areEqual("static A2", B.staticMethod(), "Static methods should be unaffected after B.prototype.__proto__ change"); |
| assert.areEqual(instanceB1.method(), instanceB2.method(), "All instances should have been changed by B.prototype.__proto__ change"); |
| } |
| }, |
| { |
| name: "super reference in base class constructor", |
| body: function () { |
| class A { |
| constructor() { super.toString(); } |
| dontDoThis() { super.makeBugs = 1; } |
| } |
| class B { |
| constructor() { |
| this.string = super.toString(); |
| this.stringCall = super.toString.call(this); |
| this.stringLambda = (()=>super.toString())() ; |
| this.stringLambda2 = (()=>(()=>super.toString())())() ; |
| this.stringEval = eval("super.toString()"); |
| this.stringEval2 = eval("eval(\"super.toString()\")"); |
| this.stringEvalLambda = eval("(()=>super.toString())()"); |
| this.stringEvalLambdaEval = eval("(()=>eval(\"super.toString()\"))()"); |
| this.stringEval2Lambda = eval("eval(\"(()=>super.toString())()\")"); |
| this.stringLambdaEval = (()=>eval("super.toString()"))() ; |
| this.stringLambda2Eval = (()=>(()=>eval("super.toString()"))())() ; |
| |
| this.func = super.toString; |
| this.funcLambda = (()=>super.toString)() ; |
| this.funcLambda2 = (()=>(()=>super.toString)())() ; |
| this.funcEval = eval("super.toString"); |
| this.funcEval2 = eval("eval(\"super.toString\")"); |
| this.funcEvalLambda = eval("(()=>super.toString)()"); |
| this.funcEvalLambdaEval = eval("(()=>eval(\"super.toString\"))()"); |
| this.funcEval2Lambda = eval("eval(\"(()=>super.toString)()\")"); |
| this.funcLambdaEval = (()=>eval("super.toString"))() ; |
| this.funcLambda2Eval = (()=>(()=>eval("super.toString"))())() ; |
| } |
| } |
| |
| assert.areEqual("function", typeof A); |
| |
| var a = new A(); |
| var b = new B(); |
| |
| a.dontDoThis(); |
| assert.areEqual(1, a.makeBugs); |
| |
| assert.areEqual("[object Object]", b.string); |
| assert.areEqual("[object Object]", b.stringCall); |
| assert.areEqual("[object Object]", b.stringLambda); |
| assert.areEqual("[object Object]", b.stringLambda2); |
| assert.areEqual("[object Object]", b.stringEval); |
| assert.areEqual("[object Object]", b.stringEval2); |
| assert.areEqual("[object Object]", b.stringEvalLambda); |
| assert.areEqual("[object Object]", b.stringEvalLambdaEval); |
| assert.areEqual("[object Object]", b.stringEval2Lambda); |
| assert.areEqual("[object Object]", b.stringLambdaEval); |
| assert.areEqual("[object Object]", b.stringLambda2Eval); |
| |
| assert.areEqual(Object.prototype.toString, b.func); |
| assert.areEqual(Object.prototype.toString, b.funcLambda); |
| assert.areEqual(Object.prototype.toString, b.funcLambda2); |
| assert.areEqual(Object.prototype.toString, b.funcEval); |
| assert.areEqual(Object.prototype.toString, b.funcEval2); |
| assert.areEqual(Object.prototype.toString, b.funcEvalLambda); |
| assert.areEqual(Object.prototype.toString, b.funcEvalLambdaEval); |
| assert.areEqual(Object.prototype.toString, b.funcEval2Lambda); |
| assert.areEqual(Object.prototype.toString, b.funcLambdaEval); |
| assert.areEqual(Object.prototype.toString, b.funcLambda2Eval); |
| } |
| }, |
| { |
| name: "Default constructors", |
| body: function () { |
| class a { }; |
| class b extends a { }; |
| |
| assert.areEqual("class a { }", a.prototype.constructor.toString()); |
| assert.areEqual("class b extends a { }", b.prototype.constructor.toString()); |
| |
| var result = []; |
| var test = []; |
| class c { constructor() { result = [...arguments]; } }; |
| class d extends c { }; |
| new d(); |
| assert.areEqual(result, [], "Default extends ctor with no args"); |
| |
| test = [1, 2, 3]; |
| new d(...test); |
| assert.areEqual(result, test, "Default extends ctor with some args"); |
| |
| test = [-5, 4.53, "test", null, undefined, 9348579]; |
| new d(...test); |
| assert.areEqual(result, test, "Default extends ctor with different arg types"); |
| } |
| }, |
| { |
| name: "Evals and lambdas", |
| body: function () { |
| class a { method() { return "hello world"; } }; |
| class b extends a { |
| method1() { return eval("super.method()"); } |
| method2() { return eval("super['method']()"); } |
| method3() { return eval("eval('super.method();')"); } |
| method4() { return eval("x => super.method()")(); } |
| method5() { return (x => eval("super.method()"))(); } |
| method6() { return (x => x => x => super.method())()()(); } |
| method7() { return (x => eval("x => eval('super.method')"))()()(); } |
| method8() { eval(); return (x => super.method())(); } |
| method9() { eval(); return (x => function () { return eval("x => super()")(); }())();} |
| method10(){ var x = () => { eval(""); return super.method(); }; return x(); } |
| } |
| |
| let instance = new b(); |
| |
| assert.areEqual("hello world", instance.method1(), "Basic eval use"); |
| assert.areEqual("hello world", instance.method2(), "Basic eval use 2"); |
| assert.areEqual("hello world", instance.method3(), "Nested eval use"); |
| assert.areEqual("hello world", instance.method4(), "Mixed lambda and eval use, no nesting"); |
| assert.areEqual("hello world", instance.method5(), "Mixed lambda and eval use, no nesting 2"); |
| assert.areEqual("hello world", instance.method6(), "Nested lambdas and eval"); |
| assert.areEqual("hello world", instance.method7(), "Nested lambdas and nested evals"); |
| assert.areEqual("hello world", instance.method8(), "Lambda with an eval in the parent"); |
| assert.throws(function() { instance.method9(); }, ReferenceError); |
| assert.throws(function() { (x => eval('super()'))(); }, ReferenceError); |
| assert.areEqual("hello world", instance.method10(), "Lambda with an eval in the lambda"); |
| } |
| }, |
| { |
| name: "Immutable binding within class body, declarations also have normal let binding in enclosing context", |
| body: function() { |
| var c = class k { |
| reassign() { eval('k = 0; WScript.Echo(k);'); } |
| }; |
| |
| // Class name is immutable within class body. |
| var obj1 = new c(); |
| assert.throws(function() { obj1.reassign() }, TypeError); |
| |
| // Class name is also immutable within body of class declaration statement |
| class Q extends c { |
| reassign() { eval('Q = 0;'); } |
| }; |
| |
| var obj2 = new Q(); |
| assert.throws(function() { obj2.reassign() }, TypeError); |
| // Class name binding in enclosing context is mutable |
| Q = 0; |
| assert.areEqual(Q, 0, "Mutable class declaration binding"); |
| } |
| }, |
| { |
| name: "Ensure the super scope slot is emitted at the right time", |
| body: function () { |
| // Previously caused an assert in ByteCodeGen. |
| class a { method () { return "hello" } }; |
| class b extends a { method () { let a; let b; return (x => super.method()); } } |
| } |
| }, |
| { |
| name: "'super' reference in eval() and lambda", |
| body: function () { |
| class a { |
| method() {return "foo"} |
| } |
| |
| class b extends a { |
| method1() { return eval("super.method()") } |
| method2() { var method= () => super.method(); return method(); } |
| method3() { return eval("var method= () => super.method(); method();") } |
| method4() { return eval("var method=function () { return super.method()}; method();") } |
| method5() { return eval("class a{method(){return 'bar'}}; class b extends a{method(){return super.method()}};(new b()).method()") } |
| } |
| |
| let instance = new b(); |
| |
| assert.areEqual("foo",instance.method1(),"'super' in eval()"); |
| assert.areEqual("foo",instance.method2(),"'super' in lambda"); |
| assert.areEqual("foo",instance.method3(),"'super' in lambda in eval"); |
| // TODO: Re-enable the following when our behavior is correct |
| //assert.throws(function () { instance.method4()}, ReferenceError, "'super' in function body in eval"); |
| assert.areEqual("bar",instance.method5(),"'super' in class method in eval"); |
| } |
| }, |
| { |
| name: "Class method can be a generator", |
| body: function() { |
| class ClassWithGeneratorMethod { |
| *iter() { |
| for (let i of [1,2,3]) { |
| yield i; |
| } |
| } |
| }; |
| |
| let a = []; |
| for (let i of new ClassWithGeneratorMethod().iter()) { |
| a.push(i); |
| } |
| |
| assert.areEqual([1,2,3], a, ""); |
| } |
| }, |
| { |
| name: "Class method with computed name can be a generator", |
| body: function() { |
| class ClassWithGeneratorMethod { |
| *[Symbol.iterator]() { |
| for (let i of [1,2,3]) { |
| yield i; |
| } |
| } |
| }; |
| |
| let a = []; |
| for (let i of new ClassWithGeneratorMethod()) { |
| a.push(i); |
| } |
| |
| assert.areEqual([1,2,3], a, ""); |
| } |
| }, |
| { |
| name: "Class static method descriptor values", |
| body: function() { |
| class B { |
| static method() { |
| return 'abc'; |
| } |
| static ['method2']() { |
| return 'def'; |
| } |
| static get method3() { |
| return 'ghi'; |
| } |
| static get ['method4']() { |
| return 'jkl'; |
| } |
| static set method5(x) { |
| return 'mno'; |
| } |
| static set ['method6'](x) { |
| return 'pqr'; |
| } |
| static *method7() { |
| yield 'stu'; |
| } |
| static *['method8']() { |
| yield 'vwx'; |
| } |
| } |
| |
| verifyClassMember(B, 'method', 'abc'); |
| verifyClassMember(B, 'method2', 'def'); |
| verifyClassMember(B, 'method3', 'ghi', true); |
| verifyClassMember(B, 'method4', 'jkl', true); |
| verifyClassMember(B, 'method5', 'mno', false, true); |
| verifyClassMember(B, 'method6', 'pqr', false, true); |
| verifyClassMember(B, 'method7', 'stu', false, false, true); |
| verifyClassMember(B, 'method8', 'vwx', false, false, true); |
| } |
| }, |
| { |
| name: "Class method descriptor values", |
| body: function() { |
| class B { |
| method() { |
| return 'abc'; |
| } |
| ['method2']() { |
| return 'def'; |
| } |
| get method3() { |
| return 'ghi'; |
| } |
| get ['method4']() { |
| return 'jkl'; |
| } |
| set method5(x) { |
| return 'mno'; |
| } |
| set ['method6'](x) { |
| return 'pqr'; |
| } |
| *method7() { |
| yield 'stu'; |
| } |
| *['method8']() { |
| yield 'vwx'; |
| } |
| } |
| |
| verifyClassMember(B.prototype, 'method', 'abc'); |
| verifyClassMember(B.prototype, 'method2', 'def'); |
| verifyClassMember(B.prototype, 'method3', 'ghi', true); |
| verifyClassMember(B.prototype, 'method4', 'jkl', true); |
| verifyClassMember(B.prototype, 'method5', 'mno', false, true); |
| verifyClassMember(B.prototype, 'method6', 'pqr', false, true); |
| verifyClassMember(B.prototype, 'method7', 'stu', false, false, true); |
| verifyClassMember(B.prototype, 'method8', 'vwx', false, false, true); |
| } |
| }, |
| { |
| name: "Class constructor cannot be called without new keyword", |
| body: function () { |
| class A {} |
| |
| assert.throws(function() { A(); }, TypeError, "Base class constructor does not have a [[call]] slot", "Class constructor cannot be called without the new keyword"); |
| assert.throws(function() { A.call(); }, TypeError, "Base class constructor does not have a [[call]] slot", "Class constructor cannot be called without the new keyword"); |
| assert.throws(function() { A.apply(); }, TypeError, "Base class constructor does not have a [[call]] slot", "Class constructor cannot be called without the new keyword"); |
| |
| class B extends A {} |
| |
| assert.throws(function() { B(); }, TypeError, "Derived class constructor does not have a [[call]] slot", "Class constructor cannot be called without the new keyword"); |
| assert.throws(function() { B.call(); }, TypeError, "Derived class constructor does not have a [[call]] slot", "Class constructor cannot be called without the new keyword"); |
| assert.throws(function() { B.apply(); }, TypeError, "Derived class constructor does not have a [[call]] slot", "Class constructor cannot be called without the new keyword"); |
| |
| class SubArray extends Array { }; |
| |
| assert.throws(function() { SubArray(); }, TypeError, "Class derived from built-in does not have a [[call]] slot", "Class constructor cannot be called without the new keyword"); |
| assert.throws(function() { SubArray.call(); }, TypeError, "Class derived from built-in does not have a [[call]] slot", "Class constructor cannot be called without the new keyword"); |
| assert.throws(function() { SubArray.apply(); }, TypeError, "Class derived from built-in does not have a [[call]] slot", "Class constructor cannot be called without the new keyword"); |
| } |
| }, |
| { |
| name: "Class methods cannot be called as constructors", |
| body: function() { |
| class B { |
| method() { |
| return { foo: 'a' }; |
| } |
| static method2() { |
| return { foo: 'b' }; |
| } |
| } |
| |
| assert.throws(function() { new B.prototype.method(); }, TypeError, "Base class prototype method cannot be new'd", "Function is not a constructor"); |
| assert.throws(function() { new B.method2(); }, TypeError, "Base class static method cannot be new'd", "Function is not a constructor"); |
| |
| class C extends B { |
| method3() { |
| return { foo: 'c' }; |
| } |
| static method4() { |
| return { foo: 'd' }; |
| } |
| } |
| |
| assert.throws(function() { new C.prototype.method(); }, TypeError, "Base class prototype method cannot be new'd", "Function is not a constructor"); |
| assert.throws(function() { new C.method2(); }, TypeError, "Base class static method cannot be new'd", "Function is not a constructor"); |
| assert.throws(function() { new C.prototype.method3(); }, TypeError, "Derived class prototype method cannot be new'd", "Function is not a constructor"); |
| assert.throws(function() { new C.method4(); }, TypeError, "Derived class static method cannot be new'd", "Function is not a constructor"); |
| |
| class D extends Array { |
| method5() { |
| return { foo: 'e' }; |
| } |
| static method6() { |
| return { foo: 'f' }; |
| } |
| } |
| |
| assert.throws(function() { new D.prototype.method5(); }, TypeError, "Derived class prototype method cannot be new'd", "Function is not a constructor"); |
| assert.throws(function() { new D.method6(); }, TypeError, "Derived class static method cannot be new'd", "Function is not a constructor"); |
| } |
| }, |
| { |
| name: "Class static member cannot have computed name 'prototype'", |
| body: function() { |
| assert.throws(function() { eval(`class A { static ['prototype']() {} };`); }, TypeError, "Ordinary static member cannot have computed name 'prototype'", "Class static member cannot be named 'prototype'"); |
| assert.throws(function() { eval(`class A { static get ['prototype']() {} };`); }, TypeError, "Static get member cannot have computed name 'prototype'", "Class static member cannot be named 'prototype'"); |
| assert.throws(function() { eval(`class A { static set ['prototype'](x) {} };`); }, TypeError, "Static set member cannot have computed name 'prototype'", "Class static member cannot be named 'prototype'"); |
| assert.throws(function() { eval(`class A { static *['prototype']() {} };`); }, TypeError, "Static generator member cannot have computed name 'prototype'", "Class static member cannot be named 'prototype'"); |
| |
| assert.doesNotThrow(function() { eval(`class A { ['prototype']() {} };`); }, "Class member with computed name 'prototype' is fine"); |
| assert.doesNotThrow(function() { eval(`class A { get ['prototype']() {} };`); }, "Class get member with computed name 'prototype' is fine"); |
| assert.doesNotThrow(function() { eval(`class A { set ['prototype'](x) {} };`); }, "Class set member with computed name 'prototype' is fine"); |
| assert.doesNotThrow(function() { eval(`class A { *['prototype']() {} };`); }, "Class generator member with computed name 'prototype' is fine"); |
| } |
| }, |
| { |
| name: "Extends expression of a class declaration or expression is strict mode", |
| body: function() { |
| var BadClass = class extends function() { arguments.caller; } {}; |
| assert.throws(function() { Object.getPrototypeOf(BadClass).arguments; }, TypeError, "The extends expression of a class expression should be parsed in strict mode", "'arguments', 'callee' and 'caller' are restricted function properties and cannot be accessed in this context"); |
| assert.throws(function() { new BadClass(); }, TypeError, "New'ing a class with a parent constructor that throws in strict mode, should throw", "'arguments', 'callee' and 'caller' are restricted function properties and cannot be accessed in this context"); |
| |
| assert.throws(function() { eval('class WorseClass extends (function foo() { with ({}); return foo; }()) {};'); }, SyntaxError, "The extends expression of a class decl should be parsed in strict mode", "'with' statements are not allowed in strict mode"); |
| } |
| }, |
| { |
| name: "Class identifier is evaluated in strict mode", |
| body: function() { |
| assert.throws(function() { eval('class arguments {};'); }, SyntaxError, "A class may not be named arguments because assigning to arguments in strict mode is not allowed", "Invalid usage of 'arguments' in strict mode"); |
| assert.throws(function() { eval('var x = class arguments {};'); }, SyntaxError, "A class may not be named arguments because assigning to arguments in strict mode is not allowed", "Invalid usage of 'arguments' in strict mode"); |
| |
| assert.throws(function() { eval('class eval {};'); }, SyntaxError, "A class may not be named eval because assigning to arguments in strict mode is not allowed", "Invalid usage of 'eval' in strict mode"); |
| assert.throws(function() { eval('var x = class eval {};'); }, SyntaxError, "A class may not be named eval because assigning to arguments in strict mode is not allowed", "Invalid usage of 'eval' in strict mode"); |
| } |
| }, |
| { |
| name: "Classes with caller/arguments methods", |
| body: function() { |
| class ClassWithArgumentsAndCallerMethods { |
| static arguments() { return 'abc'; } |
| static caller() { return 'def'; } |
| |
| arguments() { return '123'; } |
| caller() { return '456'; } |
| } |
| |
| assert.areEqual('abc', ClassWithArgumentsAndCallerMethods.arguments(), "ClassWithArgumentsAndCallerMethods.arguments() === 'abc'"); |
| assert.areEqual('def', ClassWithArgumentsAndCallerMethods.caller(), "ClassWithArgumentsAndCallerMethods.caller() === 'def'"); |
| assert.areEqual('123', new ClassWithArgumentsAndCallerMethods().arguments(), "new ClassWithArgumentsAndCallerMethods().arguments() === '123'"); |
| assert.areEqual('456', new ClassWithArgumentsAndCallerMethods().caller(), "new ClassWithArgumentsAndCallerMethods().caller() === '456'"); |
| |
| let set_arguments = ''; |
| let set_caller = ''; |
| class ClassWithArgumentsAndCallerAccessors { |
| static get arguments() { return 'abc'; } |
| static set arguments(v) { set_arguments = v; } |
| static get caller() { return 'def'; } |
| static set caller(v) { set_caller = v; } |
| |
| get arguments() { return '123'; } |
| set arguments(v) { set_arguments = v; } |
| get caller() { return '456'; } |
| set caller(v) { set_caller = v; } |
| } |
| |
| assert.areEqual('abc', ClassWithArgumentsAndCallerAccessors.arguments, "ClassWithArgumentsAndCallerAccessors.arguments === 'abc'"); |
| assert.areEqual('def', ClassWithArgumentsAndCallerAccessors.caller, "ClassWithArgumentsAndCallerAccessors.caller === 'def'"); |
| assert.areEqual('123', new ClassWithArgumentsAndCallerAccessors().arguments, "new ClassWithArgumentsAndCallerAccessors().arguments === '123'"); |
| assert.areEqual('456', new ClassWithArgumentsAndCallerAccessors().caller, "new ClassWithArgumentsAndCallerAccessors().caller === '456'"); |
| ClassWithArgumentsAndCallerAccessors.arguments = 123 |
| assert.areEqual(123, set_arguments, "ClassWithArgumentsAndCallerAccessors.arguments = 123 (calls setter)"); |
| new ClassWithArgumentsAndCallerAccessors().arguments = 456 |
| assert.areEqual(456, set_arguments, "new ClassWithArgumentsAndCallerAccessors().arguments = 456 (calls setter)"); |
| ClassWithArgumentsAndCallerAccessors.caller = 123 |
| assert.areEqual(123, set_caller, "ClassWithArgumentsAndCallerAccessors.caller = 123 (calls setter)"); |
| new ClassWithArgumentsAndCallerAccessors().caller = 456 |
| assert.areEqual(456, set_caller, "new ClassWithArgumentsAndCallerAccessors().caller = 456 (calls setter)"); |
| |
| class ClassWithArgumentsAndCallerGeneratorMethods { |
| static *arguments() { yield 'abc'; } |
| static *caller() { yield 'def'; } |
| |
| *arguments() { yield '123'; } |
| *caller() { yield '456'; } |
| } |
| |
| let s; |
| for (s of ClassWithArgumentsAndCallerGeneratorMethods.arguments()) {} |
| assert.areEqual('abc', s, "s of ClassWithArgumentsAndCallerGeneratorMethods.arguments() === 'abc'"); |
| s; |
| for (s of ClassWithArgumentsAndCallerGeneratorMethods.caller()) {} |
| assert.areEqual('def', s, "s of ClassWithArgumentsAndCallerGeneratorMethods.caller() === 'def'"); |
| s; |
| for (s of new ClassWithArgumentsAndCallerGeneratorMethods().arguments()) {} |
| assert.areEqual('123', s, "s of new ClassWithArgumentsAndCallerGeneratorMethods().arguments() === '123'"); |
| s; |
| for (s of new ClassWithArgumentsAndCallerGeneratorMethods().caller()) {} |
| assert.areEqual('456', s, "s of new ClassWithArgumentsAndCallerGeneratorMethods().caller() === '456'"); |
| |
| class ClassWithArgumentsAndCallerComputedNameMethods { |
| static ['arguments']() { return 'abc'; } |
| static ['caller']() { return 'def'; } |
| |
| ['arguments']() { return '123'; } |
| ['caller']() { return '456'; } |
| } |
| |
| assert.areEqual('abc', ClassWithArgumentsAndCallerComputedNameMethods.arguments(), "ClassWithArgumentsAndCallerComputedNameMethods.arguments() === 'abc'"); |
| assert.areEqual('def', ClassWithArgumentsAndCallerComputedNameMethods.caller(), "ClassWithArgumentsAndCallerComputedNameMethods.caller() === 'def'"); |
| assert.areEqual('123', new ClassWithArgumentsAndCallerComputedNameMethods().arguments(), "new ClassWithArgumentsAndCallerComputedNameMethods().arguments() === '123'"); |
| assert.areEqual('456', new ClassWithArgumentsAndCallerComputedNameMethods().caller(), "new ClassWithArgumentsAndCallerComputedNameMethods().caller() === '456'"); |
| |
| class ClassWithArgumentsAndCallerComputedNameAccessors { |
| static get ['arguments']() { return 'abc'; } |
| static set ['arguments'](v) { set_arguments = v; } |
| static get ['caller']() { return 'def'; } |
| static set ['caller'](v) { set_caller = v; } |
| |
| get ['arguments']() { return '123'; } |
| set ['arguments'](v) { set_arguments = v; } |
| get ['caller']() { return '456'; } |
| set ['caller'](v) { set_caller = v; } |
| } |
| |
| assert.areEqual('abc', ClassWithArgumentsAndCallerAccessors.arguments, "ClassWithArgumentsAndCallerAccessors.arguments === 'abc'"); |
| assert.areEqual('def', ClassWithArgumentsAndCallerAccessors.caller, "ClassWithArgumentsAndCallerAccessors.caller === 'def'"); |
| assert.areEqual('123', new ClassWithArgumentsAndCallerAccessors().arguments, "new ClassWithArgumentsAndCallerAccessors().arguments === '123'"); |
| assert.areEqual('456', new ClassWithArgumentsAndCallerAccessors().caller, "new ClassWithArgumentsAndCallerAccessors().caller === '456'"); |
| ClassWithArgumentsAndCallerAccessors.arguments = 123 |
| assert.areEqual(123, set_arguments, "ClassWithArgumentsAndCallerAccessors.arguments = 123 (calls setter)"); |
| new ClassWithArgumentsAndCallerAccessors().arguments = 456 |
| assert.areEqual(456, set_arguments, "new ClassWithArgumentsAndCallerAccessors().arguments = 456 (calls setter)"); |
| ClassWithArgumentsAndCallerAccessors.caller = 123 |
| assert.areEqual(123, set_caller, "ClassWithArgumentsAndCallerAccessors.caller = 123 (calls setter)"); |
| new ClassWithArgumentsAndCallerAccessors().caller = 456 |
| assert.areEqual(456, set_caller, "new ClassWithArgumentsAndCallerAccessors().caller = 456 (calls setter)"); |
| |
| class ClassWithArgumentsAndCallerComputedNameGeneratorMethods { |
| static *['arguments']() { yield 'abc'; } |
| static *['caller']() { yield 'def'; } |
| |
| *['arguments']() { yield '123'; } |
| *['caller']() { yield '456'; } |
| } |
| |
| s; |
| for (s of ClassWithArgumentsAndCallerComputedNameGeneratorMethods.arguments()) {} |
| assert.areEqual('abc', s, "s of ClassWithArgumentsAndCallerComputedNameGeneratorMethods.arguments() === 'abc'"); |
| s; |
| for (s of ClassWithArgumentsAndCallerComputedNameGeneratorMethods.caller()) {} |
| assert.areEqual('def', s, "s of ClassWithArgumentsAndCallerComputedNameGeneratorMethods.caller() === 'def'"); |
| s; |
| for (s of new ClassWithArgumentsAndCallerComputedNameGeneratorMethods().arguments()) {} |
| assert.areEqual('123', s, "s of new ClassWithArgumentsAndCallerComputedNameGeneratorMethods().arguments() === '123'"); |
| s; |
| for (s of new ClassWithArgumentsAndCallerComputedNameGeneratorMethods().caller()) {} |
| assert.areEqual('456', s, "s of new ClassWithArgumentsAndCallerComputedNameGeneratorMethods().caller() === '456'"); |
| } |
| }, |
| { |
| name: "toString on constructor should return class declaration or expression", |
| body: function () { |
| var B = class { }; |
| var A = class A extends B { constructor (...args) { super(...args); } set x(a) { this._x = a; } set y(a) { this._y = a; } }; |
| class C { |
| set x(a) { this._x = a; } |
| set y(a) { this._y = a; } |
| }; |
| class D { constructor() {} get x() { return 0; } }; |
| var E = D; |
| |
| assert.areEqual("class A extends B { constructor (...args) { super(...args); } set x(a) { this._x = a; } set y(a) { this._y = a; } }", A.prototype.constructor.toString()); |
| assert.areEqual("class { }", B.prototype.constructor.toString()); |
| assert.areEqual("class C {\r\n set x(a) { this._x = a; }\r\n set y(a) { this._y = a; }\r\n }", C.prototype.constructor.toString()); |
| assert.areEqual("class D { constructor() {} get x() { return 0; } }", D.prototype.constructor.toString()); |
| assert.areEqual("class D { constructor() {} get x() { return 0; } }", E.prototype.constructor.toString()); |
| } |
| }, |
| { |
| name: "class getters and setters must take exactly zero and one parameters respectively", |
| body: function () { |
| assert.doesNotThrow(function () { eval("class C { get foo() { } }"); }, "Class getter with zero parameters is valid syntax", "asdf"); |
| assert.throws(function () { eval("class C { get foo(x) { } }"); }, SyntaxError, "Class getter with one parameter is invalid syntax", "Getter functions must have no parameters"); |
| assert.throws(function () { eval("class C { get foo(x, y, z) { } }"); }, SyntaxError, "Class getter with more than one parameter is invalid syntax", "Getter functions must have no parameters"); |
| |
| assert.doesNotThrow(function () { eval("class C { set foo(x) { } }"); }, "Class setter with exactly one parameter is valid syntax", "asdf"); |
| assert.throws(function () { eval("class C { set foo() { } }"); }, SyntaxError, "Class setter with zero parameters is invalid syntax", "Setter functions must have exactly one parameter"); |
| assert.throws(function () { eval("class C { set foo(x, y, z) { } }"); }, SyntaxError, "Class setter with more than one parameter is invalid syntax", "Setter functions must have exactly one parameter"); |
| } |
| }, |
| { |
| name: "class identifier is const binding inside class body", |
| body: function () { |
| assert.throws(function () { class A { constructor() { A = 0; } }; new A(); }, TypeError, "Assignment to class identifier in constructor"); |
| assert.throws(function() { new (class A { constructor() { A = 0; }}); }, TypeError, "Assignment to class identifier in constructor"); |
| assert.throws(function() { class A { m() { A = 0; } }; new A().m(); }, TypeError, "Assignment to class identifier in method"); |
| assert.throws(function() { new (class A { m() { A = 0; } }).m(); }, TypeError, "Assignment to class identifier in method" ); |
| assert.throws(function() { class A { get x() { A = 0; } }; new A().x; }, TypeError, "Assignment to class identifier in getter"); |
| assert.throws(function() { (new (class A { get x() { A = 0; } })).x; }, TypeError, "Assignment to class identifier in getter"); |
| assert.throws(function() { class A { set x(_) { A = 0; } }; new A().x = 15; }, TypeError, "Assignment to class identifier in setter"); |
| assert.throws(function() { (new (class A { set x(_) { A = 0; } })).x = 15; }, TypeError, "Assignment to class identifier in setter"); |
| } |
| }, |
| { |
| name: "`class x extends y` where `y` is an expression containing identifier `x` should be a ReferenceError", |
| body: function() { |
| var refErrorText = "Use before declaration"; |
| |
| function assert_referr(code) { |
| assert.throws(function () { eval(code) }, ReferenceError, `\n ${code}`, refErrorText); |
| } |
| |
| function assert_referrors(code) { |
| // Test a given class declaration (where class is named x) by itself and then with `var x =` and `let x =` prepended. |
| assert_referr(code); |
| assert_referr(`var x = ${code}`); |
| assert_referr(`let x = ${code}`); |
| // Run the same tests, where the class declaration is a wrapped in parens to form a class expression. |
| // In the Test262 test case, the RHS is a class expression. |
| // See: test262/test/language/statements/class/name-binding/in-extends-expression-assigned.js |
| assert_referr(`(${code})`); |
| assert_referr(`var x = (${code})`); |
| assert_referr(`let x = (${code})`); |
| } |
| |
| function id(x) { return x; }; |
| function fun(x) { return function() { return x }; } |
| |
| // Using expressions containing the `x` identifier for the extends clause |
| assert_referrors("class x extends x {}"); |
| assert_referrors("class x extends (x) {}"); |
| assert_referrors("class x extends id(x) {}"); |
| assert_referrors("class x extends fun(x) {}"); |
| |
| // Assigning the result to a different identifier |
| assert_referr("var y = class x extends x {}"); |
| assert_referr("let y = class x extends x {}"); |
| |
| // Defining `y` after the class to use default initialization (var y) or temporary deadzone (let y) |
| assert.throws(function() { |
| class x extends y {}; // y == undefined |
| var y = function() {}; |
| }, TypeError, "y is undefined", "Function is not a constructor"); |
| assert_referr(` |
| class x extends y {}; // y is not defined (temporary deadzone) |
| let y = function() {}; |
| `); |
| |
| // Using eval where the result of eval is the 'x' identifier or a value that captures the 'x' identifier |
| assert_referrors("class x extends eval('x') {}"); |
| assert_referrors("class x extends eval('(x)') {}"); |
| assert_referrors("class x extends eval('id(x)') {}"); |
| assert_referrors("class x extends eval('fun(x)') {}"); |
| } |
| }, |
| ]; |
| |
| testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" }); |
| |
| // Bug 516429 at global scope |
| class a {}; |
| a = null; // No error |
| |
| // Bug 257621 at global scope |
| assert.doesNotThrow(function () { eval('new (class {})();'); }, "Parenthesized class expressions can be new'd"); |