TypeScript
TypeScript copied to clipboard
intl.d.ts: cleanup, add missing features, fix library discrepancies
Recent work on the Intl library definitions revealed a lot of outstanding inaccuracies and inconsistencies, and so I've taken a deep dive through ECMA-402 and done some significant housekeeping.
This is a big PR, and I've itemised the changes for ease of review. The majority are straightforward and uncontroversial updates to bring things in line with the spec, and it seems sensible just to have them as one PR.
A couple of proposed changes have breaking potential or are related to the typing meta, and are marked 🚩 to indicate that they may need discussion.
Library-wide
Typings
- Re-typed remaining "option registry" interfaces to
{key: never}
, as per discussion on #56902. - Where a literal union is used more than once, it is now declared as a type in order to avoid duplication, under the
{ConstructorName}Options{PropertyName}
naming convention. This practice started to be used here and there in later lib.intl updates, but is now consistent throughout. - 🚩 Renamed a small number of remaining option literal union types from
{ConstructorName}{PropertyName}
to{ConstructorName}Options{PropertyName}
. These are exported types, so in theory this is breaking if anyone is importing those specific union types from the library; however, the maintenance and DX benefits of a universal namespace convention seem to support this change.- As at 2024-05-13, these changes cause no errors in top400/dt.
- The signature of
supportedLocalesOf
constructor methods has been harmonised, with creation of a newSupportedLocalesOptions
interface. - 🚩 Removed the type
UnicodeBCP47LanguageTag
, which is simply a primitive type alias tostring
. This gets optimised away by the compiler, and serves no useful purpose; there are no other examples of "primitive alias as usage descriptor" elsewhere in the library.- As at 2024-05-13, this change causes no errors in top400/dt.
- Added missing
prototype
properties of Intl constructors. - Constructors now all declared with
var
rather thanconst
.
Documentation
- MDN links and copied documentation have been removed throughout. I'm guessing these were added due to a sense of mistaken equivalence with the DOM APIs, but there's no case for adding them to ECMA definitions.
- Added/updated docblocks for all methods and properties of Intl prototypes.
- Docblocks for each individual option property have been removed. This declutters the library significantly, for no significant impact on DX: for self-explanatory options, the description adds nothing, whereas for complex ones, a one-sentence summary doesn't eliminate the need to read the manual.
- Docblocks for constructors have been removed.
Tests
- Conformance tests are consolidated into a single testfile per lib.intl version.
- Many of the existing intl tests were implementation tests copied from MDN, rather than type validation tests, which has been addressed.
Intl
- Moved the definition of
Intl.supportedValuesOf
from es2022 to es2023. - Added override for
Intl.getCanonicalLocales
to es2020.
Intl.Collator
- 🚩 Removed string union of valid collations. This list (CLDR) is subject to change, and exactly which collations are supported is implementation-dependent. It doesn't seem like the sort of list that should be hard-coded here.
- A list of implementation-supported collations is now accessible via es2023's
Intl.supportedValuesOf()
, if runtime validation is desired.
- A list of implementation-supported collations is now accessible via es2023's
- Moved
collation
option from es5 to es2021. - The collator options
caseFirst
andnumeric
only exist if they are supported by the implementation and are absent if not, hence these are typed as optional.
Intl.DateTimeFormat
- Removed
bigint
from argument types in format functions, as explicitly not supported. - Narrowed
ResolvedDateTimeFormatOptions
property types in es5. - Moved
hourCycle
option from es2020 to es2018. - Moved
dayPeriod
,dateStyle
,timeStyle
options from es2020 to es2021. - Progressive typing of
timeZoneName
, with values{short,long}Offset
and{short,long}Generic
moved to es2022. - Added missing part types
relatedYear
andyearName
to es2020. - Removed duplicate interface properties.
Intl.DisplayNames
- Moved the definition from es2020 to es2021.
-
DisplayNamesOptions
properties may beundefined
. - Progressive typing of
DisplayNamesOptionsType
, with valuescalendar
anddateTimeField
moved to es2022. - Moved
languageDisplay
option to es2022. - New
DisplayNameConstructor
interface.
Intl.ListFormat
- Added
ListFormatPart
interface. - Renamed some existing option literal union types from
ListFormat{PropertyName}
toListFormatOptions{PropertyName}
. - New
ListFormatConstructor
interface.
Intl.Locale
- Refactored, with the
Locale
prototype no longer being typed as a child interface ofLocaleOptions
. - New
LocaleConstructor
interface. -
LocaleOptions
properties may beundefined
. - Correctly type
Locale
properties as required/readonly. - The properties
caseFirst
andnumeric
only exist if they are supported by the implementation and are absent if not, hence these are typed as optional.
Intl.NumberFormat
- Removed
bigint
from the original definition of theformatToParts
method in es2018 (prior to the language feature's existence), and added a new override to es2020.bigint.
Intl.PluralRules
- Added the missing
selectRange
method to es2023. - Added "NumberFormat v3" options to PluralRules in es2023.
- Under
ResolvedPluralRulesOptions
,{minimum,maximum}FractionDigits
may be omitted from es2023 onwards, and these are therefore typed as optional throughout. This mirrors the equivalent change toResolvedNumberFormatOptions
in #56902. - Constructor cannot be invoked without
new
.
Intl.RelativeTimeFormat
- Added the missing
numberingSystem
option. - Partitioned part and unit types for clarity.
- Renamed some existing option literal union types from
RelativeTimeFormat{PropertyName}
toRelativeTimeFormatOptions{PropertyName}
.
Intl.Segmenter
-
Segments#containing
may returnundefined
if the specified index does not exist. - New
SegmenterConstructor
interface.
Locale methods of primitive prototypes
Arbitrary nomenclature to refer to the three types of locale method definitions within the library:
- "es5 placeholders", zero-arity placeholder methods defined in the original ECMA-262 specification, intended to be overriden by ECMA-402 definitions.
- "first-generation overrides", defined prior to es2020, which accept a
locales
parameter of typestring | readonly string[]
.- "second-generation overrides", defined in es2020 and later, which accept a
locales
parameter of typeIntl.LocalesArgument
.
- Implement the second-generation overrides for
Array.prototype.toLocaleString
andTypedArray.prototype.toLocaleString
under es2020.array, which were omitted from #57679. - 🚩 Widen or infer the type of the
options
parameter ofArray.prototype.toLocaleString
.- The current type assumes that the only objects overriding
toLocaleString
will be builtin number-like/date-like objects, whosetoLocaleString
method will pass its parameters to either NumberFormat or DateTimeFormat. - However, the standard for
Array.prototype.toLocaleString
doesn't specify this; it will pass its parameters to any object'stoLocaleString
method, including user-defined methods. All that ECMA-262 asks is that those parameters not be used for anything other than the standardtoLocaleString
parameter pattern. - Accordingly, this function should, by right, remain agnostic about its elements'
toLocaleString
implementations – providing they obey the standardised function signature. - One solution is to widen the type of the options parameter to
object
, which achieves this in a way, but doesn't type-check any option properties. - In an ideal world, we would conditionally infer the options parameter type from
T
. This gets messy whenT
is a union, since simple inference of the parameter type would be covariant and yields a union, not an intersection. - In order to force contravariant inference, the simplest solution I can see is something along the lines of...
...but there's a debate as to whether adding this level of type trickery to lib.d.ts is warranted.toLocaleString( locales?: Intl.LocalesArgument, options?: (T extends { toLocaleString(locales?: any, options?: infer O): string } ? (options: O) => void : never) extends (options: infer P) => void ? P : unknown, ): string;
- The alternative is to leave the parameter as-is.
- The current type assumes that the only objects overriding
- Move first-generation overrides
String.prototype.toLocale{Lower,Upper}Case
to es2015.core. - Ensure the
locales
argument to all first-generation overrides acceptsreadonly string[]
, as per #56513. - Use
NumberFormatOptions
in the definition ofBigInt.prototype.toLocaleString
in es2020.bigint, and remove the duplicate NumberFormat definitions. -
toLocaleString
method ofBigInt64Array
andBigUint64Array
prototypes updated to the second-generation override signature. - Update docblocks for locale methods, including es5 placeholder methods.
- 🚩 Discussion on #57679 covered the question of whether the
locales
parameter of first- and second-generation overrides should be required or optional when an es5 placeholder already exists.- The verdict there was that the first parameter should be required, so that the new override and the es5 placeholder don't have overlapping signatures.
- However, this disallows usage like the following:
...which is a valid way of selecting the default locale while also specifying custom format options. Keeping the first parameter optional is an easy way of allowing this usage case.numberArray.toLocaleString(undefined, { minimumIntegerDigits: 3 });
- It's only the array methods added by the above PR that currently use the "mandatory first parameter" signature; all other existing first- and second-generation overrides have the locales parameter as optional.