TypeScript icon indicating copy to clipboard operation
TypeScript copied to clipboard

intl.d.ts: cleanup, add missing features, fix library discrepancies

Open Renegade334 opened this issue 10 months ago • 17 comments

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 new SupportedLocalesOptions interface.
  • 🚩 Removed the type UnicodeBCP47LanguageTag, which is simply a primitive type alias to string. 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 than const.

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.
  • Moved collation option from es5 to es2021.
  • The collator options caseFirst and numeric 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 and yearName to es2020.
  • Removed duplicate interface properties.

Intl.DisplayNames

  • Moved the definition from es2020 to es2021.
  • DisplayNamesOptions properties may be undefined.
  • Progressive typing of DisplayNamesOptionsType, with values calendar and dateTimeField 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} to ListFormatOptions{PropertyName}.
  • New ListFormatConstructor interface.

Intl.Locale

  • Refactored, with the Locale prototype no longer being typed as a child interface of LocaleOptions.
  • New LocaleConstructor interface.
  • LocaleOptions properties may be undefined.
  • Correctly type Locale properties as required/readonly.
  • The properties caseFirst and numeric 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 the formatToParts 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 to ResolvedNumberFormatOptions 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} to RelativeTimeFormatOptions{PropertyName}.

Intl.Segmenter

  • Segments#containing may return undefined 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 type string | readonly string[].
  • "second-generation overrides", defined in es2020 and later, which accept a locales parameter of type Intl.LocalesArgument.
  • Implement the second-generation overrides for Array.prototype.toLocaleString and TypedArray.prototype.toLocaleString under es2020.array, which were omitted from #57679.
  • 🚩 Widen or infer the type of the options parameter of Array.prototype.toLocaleString.
    • The current type assumes that the only objects overriding toLocaleString will be builtin number-like/date-like objects, whose toLocaleString 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's toLocaleString method, including user-defined methods. All that ECMA-262 asks is that those parameters not be used for anything other than the standard toLocaleString 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 when T 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...
      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;
      
      ...but there's a debate as to whether adding this level of type trickery to lib.d.ts is warranted.
    • The alternative is to leave the parameter as-is.
  • Move first-generation overrides String.prototype.toLocale{Lower,Upper}Case to es2015.core.
  • Ensure the locales argument to all first-generation overrides accepts readonly string[], as per #56513.
  • Use NumberFormatOptions in the definition of BigInt.prototype.toLocaleString in es2020.bigint, and remove the duplicate NumberFormat definitions.
  • toLocaleString method of BigInt64Array and BigUint64Array 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:
      numberArray.toLocaleString(undefined, { minimumIntegerDigits: 3 });
      
      ...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.
    • 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.

Renegade334 avatar Apr 05 '24 00:04 Renegade334