forked from mozilla/gecko-dev
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Bug 1566143 - Add tests for Optional Chaining r=arai
Differential Revision: https://phabricator.services.mozilla.com/D59870 --HG-- extra : moz-landing-system : lando
- Loading branch information
Showing
46 changed files
with
1,031 additions
and
39 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,226 @@ | ||
var BUGNUMBER = 1566143; | ||
var summary = "Implement the Optional Chain operator (?.) proposal"; | ||
|
||
print(BUGNUMBER + ": " + summary); | ||
|
||
// These tests are originally from webkit. | ||
// webkit specifics have been removed and error messages have been updated. | ||
function shouldBe(actual, expected) { | ||
if (actual !== expected) | ||
throw new Error(`expected ${expected} but got ${actual}`); | ||
} | ||
|
||
function shouldThrowSyntaxError(script) { | ||
let error; | ||
try { | ||
eval(script); | ||
} catch (e) { | ||
error = e; | ||
} | ||
|
||
if (!(error instanceof SyntaxError)) | ||
throw new Error('Expected SyntaxError!'); | ||
} | ||
|
||
function shouldNotThrowSyntaxError(script) { | ||
let error; | ||
try { | ||
eval(script); | ||
} catch (e) { | ||
error = e; | ||
} | ||
|
||
if ((error instanceof SyntaxError)) | ||
throw new Error('Unxpected SyntaxError!'); | ||
} | ||
|
||
function shouldThrowTypeError(func, messagePrefix) { | ||
let error; | ||
try { | ||
func(); | ||
} catch (e) { | ||
error = e; | ||
} | ||
|
||
if (!(error instanceof TypeError)) | ||
throw new Error('Expected TypeError!'); | ||
|
||
if (!error.message.startsWith(messagePrefix)) | ||
throw new Error(`TypeError has wrong message!, expected ${messagePrefix} but got ${error.message}`); | ||
} | ||
function testBasicSuccessCases() { | ||
shouldBe(undefined?.valueOf(), undefined); | ||
shouldBe(null?.valueOf(), undefined); | ||
shouldBe(true?.valueOf(), true); | ||
shouldBe(false?.valueOf(), false); | ||
shouldBe(0?.valueOf(), 0); | ||
shouldBe(1?.valueOf(), 1); | ||
shouldBe(''?.valueOf(), ''); | ||
shouldBe('hi'?.valueOf(), 'hi'); | ||
shouldBe(({})?.constructor, Object); | ||
shouldBe(({ x: 'hi' })?.x, 'hi'); | ||
shouldBe([]?.length, 0); | ||
shouldBe(['hi']?.length, 1); | ||
|
||
shouldBe(undefined?.['valueOf'](), undefined); | ||
shouldBe(null?.['valueOf'](), undefined); | ||
shouldBe(true?.['valueOf'](), true); | ||
shouldBe(false?.['valueOf'](), false); | ||
shouldBe(0?.['valueOf'](), 0); | ||
shouldBe(1?.['valueOf'](), 1); | ||
shouldBe(''?.['valueOf'](), ''); | ||
shouldBe('hi'?.['valueOf'](), 'hi'); | ||
shouldBe(({})?.['constructor'], Object); | ||
shouldBe(({ x: 'hi' })?.['x'], 'hi'); | ||
shouldBe([]?.['length'], 0); | ||
shouldBe(['hi']?.[0], 'hi'); | ||
|
||
shouldBe(undefined?.(), undefined); | ||
shouldBe(null?.(), undefined); | ||
shouldBe((() => 3)?.(), 3); | ||
} | ||
|
||
function testBasicFailureCases() { | ||
shouldThrowTypeError(() => true?.(), 'true is not a function'); | ||
shouldThrowTypeError(() => false?.(), 'false is not a function'); | ||
shouldThrowTypeError(() => 0?.(), '0 is not a function'); | ||
shouldThrowTypeError(() => 1?.(), '1 is not a function'); | ||
shouldThrowTypeError(() => ''?.(), '"" is not a function'); | ||
shouldThrowTypeError(() => 'hi'?.(), '"hi" is not a function'); | ||
shouldThrowTypeError(() => ({})?.(), '({}) is not a function'); | ||
shouldThrowTypeError(() => ({ x: 'hi' })?.(), '({x:"hi"}) is not a function'); | ||
shouldThrowTypeError(() => []?.(), '[] is not a function'); | ||
shouldThrowTypeError(() => ['hi']?.(), '[...] is not a function'); | ||
} | ||
|
||
testBasicSuccessCases(); | ||
|
||
testBasicFailureCases(); | ||
|
||
shouldThrowTypeError(() => ({})?.i(), '(intermediate value).i is not a function'); | ||
shouldBe(({}).i?.(), undefined); | ||
shouldBe(({})?.i?.(), undefined); | ||
shouldThrowTypeError(() => ({})?.['i'](), '(intermediate value)["i"] is not a function'); | ||
shouldBe(({})['i']?.(), undefined); | ||
shouldBe(({})?.['i']?.(), undefined); | ||
|
||
shouldThrowTypeError(() => ({})?.a['b'], 'can\'t access property "b", (intermediate value).a is undefined'); | ||
shouldBe(({})?.a?.['b'], undefined); | ||
shouldBe(null?.a['b']().c, undefined); | ||
shouldThrowTypeError(() => ({})?.['a'].b, 'can\'t access property "b", (intermediate value)["a"] is undefined'); | ||
shouldBe(({})?.['a']?.b, undefined); | ||
shouldBe(null?.['a'].b()['c'], undefined); | ||
shouldBe(null?.()().a['b'], undefined); | ||
|
||
const o0 = { a: { b() { return this._b.bind(this); }, _b() { return this.__b; }, __b: { c: 42 } } }; | ||
shouldBe(o0?.a?.['b']?.()?.()?.c, 42); | ||
shouldBe(o0?.i?.['j']?.()?.()?.k, undefined); | ||
shouldBe((o0.a?._b)?.().c, 42); | ||
shouldBe((o0.a?._b)().c, 42); | ||
shouldBe((o0.a?.b?.())?.().c, 42); | ||
shouldBe((o0.a?.['b']?.())?.().c, 42); | ||
|
||
shouldBe(({ undefined: 3 })?.[null?.a], 3); | ||
shouldBe((() => 3)?.(null?.a), 3); | ||
|
||
const o1 = { count: 0, get x() { this.count++; return () => {}; } }; | ||
o1.x?.y; | ||
shouldBe(o1.count, 1); | ||
o1.x?.['y']; | ||
shouldBe(o1.count, 2); | ||
o1.x?.(); | ||
shouldBe(o1.count, 3); | ||
null?.(o1.x); | ||
shouldBe(o1.count, 3); | ||
|
||
shouldBe(delete undefined?.foo, true); | ||
shouldBe(delete null?.foo, true); | ||
shouldBe(delete undefined?.['foo'], true); | ||
shouldBe(delete null?.['foo'], true); | ||
shouldBe(delete undefined?.(), true); | ||
shouldBe(delete null?.(), true); | ||
shouldBe(delete ({}).a?.b?.b, true); | ||
shouldBe(delete ({a : {b: undefined}}).a?.b?.b, true); | ||
shouldBe(delete ({a : {b: undefined}}).a?.["b"]?.["b"], true); | ||
|
||
const o2 = { x: 0, y: 0, z() {} }; | ||
shouldBe(delete o2?.x, true); | ||
shouldBe(o2.x, undefined); | ||
shouldBe(o2.y, 0); | ||
shouldBe(delete o2?.x, true); | ||
shouldBe(delete o2?.['y'], true); | ||
shouldBe(o2.y, undefined); | ||
shouldBe(delete o2?.['y'], true); | ||
shouldBe(delete o2.z?.(), true); | ||
|
||
function greet(name) { return `hey, ${name}${this.suffix ?? '.'}`; } | ||
shouldBe(eval?.('greet("world")'), 'hey, world.'); | ||
shouldBe(greet?.call({ suffix: '!' }, 'world'), 'hey, world!'); | ||
shouldBe(greet.call?.({ suffix: '!' }, 'world'), 'hey, world!'); | ||
shouldBe(null?.call({ suffix: '!' }, 'world'), undefined); | ||
shouldBe(({}).call?.({ suffix: '!' }, 'world'), undefined); | ||
shouldBe(greet?.apply({ suffix: '?' }, ['world']), 'hey, world?'); | ||
shouldBe(greet.apply?.({ suffix: '?' }, ['world']), 'hey, world?'); | ||
shouldBe(null?.apply({ suffix: '?' }, ['world']), undefined); | ||
shouldBe(({}).apply?.({ suffix: '?' }, ['world']), undefined); | ||
shouldThrowSyntaxError('class C {} class D extends C { foo() { return super?.bar; } }'); | ||
shouldThrowSyntaxError('class C {} class D extends C { foo() { return super?.["bar"]; } }'); | ||
shouldThrowSyntaxError('class C {} class D extends C { constructor() { super?.(); } }'); | ||
shouldThrowSyntaxError('const o = { C: class {} }; new o?.C();') | ||
shouldThrowSyntaxError('const o = { C: class {} }; new o?.["C"]();') | ||
shouldThrowSyntaxError('class C {} new C?.();') | ||
shouldThrowSyntaxError('function foo() { new?.target; }'); | ||
shouldThrowSyntaxError('function tag() {} tag?.``;'); | ||
shouldThrowSyntaxError('const o = { tag() {} }; o?.tag``;'); | ||
|
||
// NOT an optional chain | ||
shouldBe(false?.4:5, 5); | ||
|
||
function testSideEffectCountFunction() { | ||
let count = 0; | ||
let a = { | ||
b: { | ||
c: { | ||
d: () => { | ||
count++; | ||
return a; | ||
} | ||
} | ||
} | ||
} | ||
|
||
a.b.c.d?.()?.b?.c?.d | ||
|
||
shouldBe(count, 1); | ||
} | ||
|
||
function testSideEffectCountGetters() { | ||
let count = 0; | ||
let a = { | ||
get b() { | ||
count++; | ||
return { c: {} }; | ||
} | ||
} | ||
|
||
a.b?.c?.d; | ||
shouldBe(count, 1); | ||
a.b?.c?.d; | ||
shouldBe(count, 2); | ||
} | ||
|
||
testSideEffectCountFunction(); | ||
testSideEffectCountGetters(); | ||
|
||
// stress test SM | ||
shouldBe(({a : {b: undefined}}).a.b?.()()(), undefined); | ||
shouldBe(({a : {b: undefined}}).a.b?.()?.()(), undefined); | ||
shouldBe(({a : {b: () => undefined}}).a.b?.()?.(), undefined); | ||
shouldThrowTypeError(() => delete ({a : {b: undefined}}).a?.b.b.c, 'can\'t access property "b", (intermediate value).a.b is undefined'); | ||
shouldBe(delete ({a : {b: undefined}}).a?.["b"]?.["b"], true); | ||
shouldThrowTypeError(() => (({a : {b: () => undefined}}).a.b?.())(), 'undefined is not a function'); | ||
|
||
if (typeof reportCompare === "function") | ||
reportCompare(true, true); | ||
|
||
print("Tests complete"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
...nguage/expressions/assignment/dstr/array-elem-nested-memberexpr-optchain-prop-ref-init.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
57 changes: 57 additions & 0 deletions
57
...62/language/expressions/assignment/dstr/array-elem-nested-memberexpr-optchain-prop-ref.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
// |reftest| error:SyntaxError | ||
// This file was procedurally generated from the following sources: | ||
// - src/dstr-assignment/array-elem-nested-memberexpr-optchain-prop-ref.case | ||
// - src/dstr-assignment/syntax/assignment-expr.template | ||
/*--- | ||
description: It is a Syntax Error if LeftHandSideExpression of an DestructuringAssignmentTarget is neither an ObjectLiteral nor an ArrayLiteral and AssignmentTargetType(LeftHandSideExpression) is not simple Using Object (MemberExpression OptionalChain .IdentifierName) (AssignmentExpression) | ||
esid: sec-variable-statement-runtime-semantics-evaluation | ||
features: [optional-chaining, destructuring-binding] | ||
flags: [generated] | ||
negative: | ||
phase: parse | ||
type: SyntaxError | ||
info: | | ||
VariableDeclaration : BindingPattern Initializer | ||
1. Let rhs be the result of evaluating Initializer. | ||
2. Let rval be GetValue(rhs). | ||
3. ReturnIfAbrupt(rval). | ||
4. Return the result of performing BindingInitialization for | ||
BindingPattern passing rval and undefined as arguments. | ||
Syntax | ||
AssignmentElement : DestructuringAssignmentTarget Initializer_opt | ||
DestructuringAssignmentTarget : LeftHandSideExpression | ||
Static Semantics: Early Errors | ||
OptionalExpression: | ||
MemberExpression OptionalChain | ||
CallExpression OptionalChain | ||
OptionalExpression OptionalChain | ||
OptionalChain: | ||
?. [ Expression ] | ||
?. IdentifierName | ||
?. Arguments | ||
?. TemplateLiteral | ||
OptionalChain [ Expression ] | ||
OptionalChain .IdentifierName | ||
OptionalChain Arguments | ||
OptionalChain TemplateLiteral | ||
DestructuringAssignmentTarget : LeftHandSideExpression | ||
- It is a Syntax Error if LeftHandSideExpression is neither an ObjectLiteral nor an ArrayLiteral and IsValidSimpleAssignmentTarget(LeftHandSideExpression) is not true. | ||
Static Semantics: IsValidSimpleAssignmentTarget | ||
LeftHandSideExpression : OptionalExpression | ||
1. Return false. | ||
---*/ | ||
$DONOTEVALUATE(); | ||
var x = {}; | ||
|
||
0, [x?.y] = [23]; |
2 changes: 1 addition & 1 deletion
2
...language/expressions/assignment/dstr/array-elem-put-obj-literal-optchain-prop-ref-init.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
60 changes: 60 additions & 0 deletions
60
...t262/language/expressions/assignment/dstr/array-elem-put-obj-literal-optchain-prop-ref.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
// |reftest| error:SyntaxError | ||
// This file was procedurally generated from the following sources: | ||
// - src/dstr-assignment/array-elem-put-obj-literal-optchain-prop-ref.case | ||
// - src/dstr-assignment/syntax/assignment-expr.template | ||
/*--- | ||
description: It is a Syntax Error if LeftHandSideExpression of an DestructuringAssignmentTarget is neither an ObjectLiteral nor an ArrayLiteral and AssignmentTargetType(LeftHandSideExpression) is not simple Using Object (AssignmentExpression) | ||
esid: sec-variable-statement-runtime-semantics-evaluation | ||
features: [optional-chaining, destructuring-binding] | ||
flags: [generated] | ||
negative: | ||
phase: parse | ||
type: SyntaxError | ||
info: | | ||
VariableDeclaration : BindingPattern Initializer | ||
1. Let rhs be the result of evaluating Initializer. | ||
2. Let rval be GetValue(rhs). | ||
3. ReturnIfAbrupt(rval). | ||
4. Return the result of performing BindingInitialization for | ||
BindingPattern passing rval and undefined as arguments. | ||
Syntax | ||
AssignmentElement : DestructuringAssignmentTarget Initializer_opt | ||
DestructuringAssignmentTarget : LeftHandSideExpression | ||
Static Semantics: Early Errors | ||
OptionalExpression: | ||
MemberExpression OptionalChain | ||
CallExpression OptionalChain | ||
OptionalExpression OptionalChain | ||
OptionalChain: | ||
?. [ Expression ] | ||
?. IdentifierName | ||
?. Arguments | ||
?. TemplateLiteral | ||
OptionalChain [ Expression ] | ||
OptionalChain .IdentifierName | ||
OptionalChain Arguments | ||
OptionalChain TemplateLiteral | ||
DestructuringAssignmentTarget : LeftHandSideExpression | ||
- It is a Syntax Error if LeftHandSideExpression is neither an ObjectLiteral nor an ArrayLiteral and IsValidSimpleAssignmentTarget(LeftHandSideExpression) is not true. | ||
Static Semantics: IsValidSimpleAssignmentTarget | ||
LeftHandSideExpression : OptionalExpression | ||
1. Return false. | ||
---*/ | ||
$DONOTEVALUATE(); | ||
|
||
0, [{ | ||
set y(val) { | ||
throw new Test262Error('The property should not be accessed.'); | ||
} | ||
}?.y] = [23]; |
2 changes: 1 addition & 1 deletion
2
...age/expressions/assignment/dstr/obj-prop-elem-target-memberexpr-optchain-prop-ref-init.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.