From 10d03d988ac2841d429733259a011af8996bc655 Mon Sep 17 00:00:00 2001 From: Justin Grant Date: Sat, 20 Jun 2020 13:51:44 -0700 Subject: [PATCH] Require `options` bags to be object or undefined See also: https://github.com/tc39/ecma402/issues/480 Co-Authored-By: Philip Chimento --- polyfill/lib/ecmascript.mjs | 16 +++++++++++---- polyfill/test/ecmascript.mjs | 10 +++++++++- polyfill/test/yearmonth.mjs | 2 +- spec/abstractops.html | 38 +++++++++++++++++++++++++----------- 4 files changed, 49 insertions(+), 17 deletions(-) diff --git a/polyfill/lib/ecmascript.mjs b/polyfill/lib/ecmascript.mjs index b005418916..3c7d809087 100644 --- a/polyfill/lib/ecmascript.mjs +++ b/polyfill/lib/ecmascript.mjs @@ -6,7 +6,6 @@ import Call from 'es-abstract/2019/Call.js'; import SpeciesConstructor from 'es-abstract/2019/SpeciesConstructor.js'; import ToInteger from 'es-abstract/2019/ToInteger.js'; import ToNumber from 'es-abstract/2019/ToNumber.js'; -import ToObject from 'es-abstract/2019/ToObject.js'; import ToPrimitive from 'es-abstract/2019/ToPrimitive.js'; import ToString from 'es-abstract/2019/ToString.js'; @@ -55,7 +54,6 @@ const ES2019 = { SpeciesConstructor, ToInteger, ToNumber, - ToObject, ToPrimitive, ToString }; @@ -466,15 +464,19 @@ export const ES = ObjectAssign({}, ES2019, { return duration; }, ToDurationTemporalDisambiguation: (options) => { + options = ES.NormalizeOptionsObject(options); return ES.GetOption(options, 'disambiguation', ['constrain', 'balance', 'reject'], 'constrain'); }, ToTemporalDisambiguation: (options) => { + options = ES.NormalizeOptionsObject(options); return ES.GetOption(options, 'disambiguation', ['constrain', 'reject'], 'constrain'); }, ToTimeZoneTemporalDisambiguation: (options) => { + options = ES.NormalizeOptionsObject(options); return ES.GetOption(options, 'disambiguation', ['compatible', 'earlier', 'later', 'reject'], 'compatible'); }, ToDurationSubtractionTemporalDisambiguation: (options) => { + options = ES.NormalizeOptionsObject(options); return ES.GetOption(options, 'disambiguation', ['balanceConstrain', 'balance'], 'balanceConstrain'); }, ToLargestTemporalUnit: (options, fallback, disallowedStrings = []) => { @@ -493,6 +495,7 @@ export const ES = ObjectAssign({}, ES2019, { for (const s of disallowedStrings) { allowed.delete(s); } + options = ES.NormalizeOptionsObject(options); return ES.GetOption(options, 'largestUnit', [...allowed], fallback); }, ToPartialRecord: (bag, fields) => { @@ -1581,9 +1584,14 @@ export const ES = ObjectAssign({}, ES2019, { return new TemporalTimeZone(ES.TemporalTimeZoneFromString(fmt.resolvedOptions().timeZone)); }, ComparisonResult: (value) => (value < 0 ? -1 : value > 0 ? 1 : value), + NormalizeOptionsObject: (options) => { + if (options === undefined) return {}; + if (options !== null && (typeof options === 'object' || typeof options === 'function')) { + return options; + } + throw new TypeError(`Options parameter must be an object, not a ${typeof options}`); + }, GetOption: (options, property, allowedValues, fallback) => { - if (options === null || options === undefined) return fallback; - options = ES.ToObject(options); let value = options[property]; if (value !== undefined) { value = ES.ToString(value); diff --git a/polyfill/test/ecmascript.mjs b/polyfill/test/ecmascript.mjs index 835d9ef60d..2e6125e6e0 100644 --- a/polyfill/test/ecmascript.mjs +++ b/polyfill/test/ecmascript.mjs @@ -5,7 +5,7 @@ import Pretty from '@pipobscure/demitasse-pretty'; const { reporter } = Pretty; import { strict as assert } from 'assert'; -const { deepEqual } = assert; +const { deepEqual, throws } = assert; import { ES } from '../lib/ecmascript.mjs'; import { GetSlot, TIMEZONE_ID } from '../lib/slots.mjs'; @@ -398,6 +398,14 @@ describe('ECMAScript', () => { it(`${nanos} @ ${zone}`, () => deepEqual(ES.GetFormatterParts(zone, nanos), expected)); } }); + + describe('NormalizeOptionsObject', () => { + it('Options parameter can only be an object or undefined', () => { + [null, 1, 'hello', true, Symbol('1'), 1n].forEach((options) => + throws(() => ES.NormalizeOptionsObject(options), TypeError) + ); + }); + }); }); import { normalize } from 'path'; diff --git a/polyfill/test/yearmonth.mjs b/polyfill/test/yearmonth.mjs index 075862e948..14de8407cf 100644 --- a/polyfill/test/yearmonth.mjs +++ b/polyfill/test/yearmonth.mjs @@ -357,7 +357,7 @@ describe('YearMonth', () => { describe('YearMonth.with()', () => { it('throws on bad disambiguation', () => { ['', 'CONSTRAIN', 'balance', 3, null].forEach((disambiguation) => - throws(() => YearMonth.from(2019, 1).with({ month: 2 }, { disambiguation }), RangeError) + throws(() => YearMonth.from({ year: 2019, month: 1 }).with({ month: 2 }, { disambiguation }), RangeError) ); }); }); diff --git a/spec/abstractops.html b/spec/abstractops.html index 1f511a18a2..b8629299bb 100644 --- a/spec/abstractops.html +++ b/spec/abstractops.html @@ -4,31 +4,43 @@

Abstract operations

- + + +

NormalizeOptionsObject ( _options_ )

+

+ The abstract operation NormalizeOptionsObject massages _options_ into an Object to be subsequently passed to GetOption. + It throws a TypeError if _options_ is not undefined and not an Object. +

+ + 1. If _options_ is *undefined*, then + 1. Return ! ObjectCreate(*null*). + 1. If Type(_options_) is Object, then + 1. Return _options_. + 1. Throw a *TypeError* exception. + +
+

GetOption ( _options_, _property_, _values_, _fallback_ )

- The abstract operation GetOption extracts the value of the property named _property_ from the provided _options_ object, converts it to a string, checks whether it is one of a List of allowed _values_, and fills in a _fallback_ value if necessary. If _values_ is *undefined*, there is no fixed set of values and any is permitted. If _options_ is *undefined*, then _fallback_ is returned. + The abstract operation GetOption extracts the value of the property named _property_ from the provided _options_ object, converts it to a string, checks whether it is one of a List of allowed _values_, and fills in a _fallback_ value if necessary. If _values_ is *undefined*, there is no fixed set of values and any is permitted.

- 1. If _options_ is *undefined* or *null*, then - 1. Return _fallback_. - 1. Set _options_ to ? ToObject(_options_). + 1. Assert: Type(_options_) is Object. 1. Let _value_ be ? Get(_options_, _property_). - 1. If _value_ is not *undefined*, then - 1. Set _value_ to ? ToString(_value_). - 1. If _values_ is not *undefined*, then - 1. If _values_ does not contain an element equal to _value_, throw a *RangeError* exception. - 1. Return _value_. - 1. Else, return _fallback_. + 1. If _value_ is *undefined*, return _fallback_. + 1. Let _value_ be ? ToString(_value_). + 1. If _values_ is not *undefined* and _values_ does not contain an element equal to _value_, throw a *RangeError* exception. + 1. Return _value_.

ToDurationTemporalDisambiguation ( _options_ )

+ 1. Set _options_ to ? NormalizeOptionsObject(_options_). 1. Return ? GetOption(_options_, *"disambiguation"*, « *"constrain"*, *"balance"*, *"reject"* », *"constrain"*).
@@ -36,6 +48,7 @@

ToDurationTemporalDisambiguation ( _options_ )

ToTemporalDisambiguation ( _options_ )

+ 1. Set _options_ to ? NormalizeOptionsObject(_options_). 1. Return ? GetOption(_options_, *"disambiguation"*, « *"constrain"*, *"reject"* », *"constrain"*).
@@ -43,6 +56,7 @@

ToTemporalDisambiguation ( _options_ )

ToTimeZoneTemporalDisambiguation ( _options_ )

+ 1. Set _options_ to ? NormalizeOptionsObject(_options_). 1. Return ? GetOption(_options_, *"disambiguation"*, « *"compatible"*, *"earlier"*, *"later"*, *"reject"* », *"compatible"*).
@@ -50,6 +64,7 @@

ToTimeZoneTemporalDisambiguation ( _options_ )

ToDurationSubtractionTemporalDisambiguation ( _options_ )

+ 1. Set _options_ to ? NormalizeOptionsObject(_options_). 1. Return ? GetOption(_options_, *"disambiguation"*, « *"balanceConstrain"*, *"balance"* », *"balanceConstrain"*).
@@ -58,6 +73,7 @@

ToDurationSubtractionTemporalDisambiguation ( _options_ )

ToLargestTemporalUnit ( _largestUnit_, _disallowedUnits_, _defaultUnit_ )

1. Assert: _disallowedUnits_ does not contain _defaultUnit_. + 1. Set _options_ to ? NormalizeOptionsObject(_options_). 1. Let _largestUnit_ be GetOption(_options_, *"largestUnit"*, « *"years"*, *"months"*, *"days"*, *"hours"*, *"minutes"*, *"seconds"*, *"milliseconds"*, *"microseconds"*, *"nanoseconds"* », _defaultUnit_). 1. If _disallowedUnits_ contains _largestUnit_, then 1. Throw a *RangeError* exception.