fix(common): mark locale data arrays as readonly (#30397)

To discourage developers from mutating the arrays returned
from the following methods, their return types have been marked
as readonly.

* `getLocaleDayPeriods()`
* `getLocaleDayNames()`
* `getLocaleMonthNames()`
* `getLocaleEraNames()`

Fixes #27003

BREAKING CHANGE:
The locale data API has been marked as returning readonly arrays, rather
than mutable arrays, since these arrays are shared across calls to the
API. If you were mutating them (e.g. calling `sort()`, `push()`, `splice()`, etc)
then your code will not longer compile. If you need to mutate the array, you
should now take a copy (e.g. by calling `slice()`) and mutate the copy.

PR Close #30397
This commit is contained in:
Artem Halas 2020-07-12 20:37:19 +03:00 committed by Andrew Kushnir
parent 2d52c80332
commit 6acea54f62
3 changed files with 101 additions and 10 deletions

View File

@ -61,13 +61,13 @@ export declare function getLocaleDateFormat(locale: string, width: FormatWidth):
export declare function getLocaleDateTimeFormat(locale: string, width: FormatWidth): string; export declare function getLocaleDateTimeFormat(locale: string, width: FormatWidth): string;
export declare function getLocaleDayNames(locale: string, formStyle: FormStyle, width: TranslationWidth): string[]; export declare function getLocaleDayNames(locale: string, formStyle: FormStyle, width: TranslationWidth): ReadonlyArray<string>;
export declare function getLocaleDayPeriods(locale: string, formStyle: FormStyle, width: TranslationWidth): [string, string]; export declare function getLocaleDayPeriods(locale: string, formStyle: FormStyle, width: TranslationWidth): Readonly<[string, string]>;
export declare function getLocaleDirection(locale: string): 'ltr' | 'rtl'; export declare function getLocaleDirection(locale: string): 'ltr' | 'rtl';
export declare function getLocaleEraNames(locale: string, width: TranslationWidth): [string, string]; export declare function getLocaleEraNames(locale: string, width: TranslationWidth): Readonly<[string, string]>;
export declare function getLocaleExtraDayPeriodRules(locale: string): (Time | [Time, Time])[]; export declare function getLocaleExtraDayPeriodRules(locale: string): (Time | [Time, Time])[];
@ -77,7 +77,7 @@ export declare function getLocaleFirstDayOfWeek(locale: string): WeekDay;
export declare function getLocaleId(locale: string): string; export declare function getLocaleId(locale: string): string;
export declare function getLocaleMonthNames(locale: string, formStyle: FormStyle, width: TranslationWidth): string[]; export declare function getLocaleMonthNames(locale: string, formStyle: FormStyle, width: TranslationWidth): ReadonlyArray<string>;
export declare function getLocaleNumberFormat(locale: string, type: NumberFormatStyle): string; export declare function getLocaleNumberFormat(locale: string, type: NumberFormatStyle): string;

View File

@ -233,7 +233,7 @@ export function getLocaleId(locale: string): string {
* @publicApi * @publicApi
*/ */
export function getLocaleDayPeriods( export function getLocaleDayPeriods(
locale: string, formStyle: FormStyle, width: TranslationWidth): [string, string] { locale: string, formStyle: FormStyle, width: TranslationWidth): Readonly<[string, string]> {
const data = ɵfindLocaleData(locale); const data = ɵfindLocaleData(locale);
const amPmData = <[string, string][][]>[ const amPmData = <[string, string][][]>[
data[ɵLocaleDataIndex.DayPeriodsFormat], data[ɵLocaleDataIndex.DayPeriodsStandalone] data[ɵLocaleDataIndex.DayPeriodsFormat], data[ɵLocaleDataIndex.DayPeriodsStandalone]
@ -255,7 +255,7 @@ export function getLocaleDayPeriods(
* @publicApi * @publicApi
*/ */
export function getLocaleDayNames( export function getLocaleDayNames(
locale: string, formStyle: FormStyle, width: TranslationWidth): string[] { locale: string, formStyle: FormStyle, width: TranslationWidth): ReadonlyArray<string> {
const data = ɵfindLocaleData(locale); const data = ɵfindLocaleData(locale);
const daysData = const daysData =
<string[][][]>[data[ɵLocaleDataIndex.DaysFormat], data[ɵLocaleDataIndex.DaysStandalone]]; <string[][][]>[data[ɵLocaleDataIndex.DaysFormat], data[ɵLocaleDataIndex.DaysStandalone]];
@ -276,7 +276,7 @@ export function getLocaleDayNames(
* @publicApi * @publicApi
*/ */
export function getLocaleMonthNames( export function getLocaleMonthNames(
locale: string, formStyle: FormStyle, width: TranslationWidth): string[] { locale: string, formStyle: FormStyle, width: TranslationWidth): ReadonlyArray<string> {
const data = ɵfindLocaleData(locale); const data = ɵfindLocaleData(locale);
const monthsData = const monthsData =
<string[][][]>[data[ɵLocaleDataIndex.MonthsFormat], data[ɵLocaleDataIndex.MonthsStandalone]]; <string[][][]>[data[ɵLocaleDataIndex.MonthsFormat], data[ɵLocaleDataIndex.MonthsStandalone]];
@ -287,7 +287,6 @@ export function getLocaleMonthNames(
/** /**
* Retrieves Gregorian-calendar eras for the given locale. * Retrieves Gregorian-calendar eras for the given locale.
* @param locale A locale code for the locale format rules to use. * @param locale A locale code for the locale format rules to use.
* @param formStyle The required grammatical form.
* @param width The required character width. * @param width The required character width.
* @returns An array of localized era strings. * @returns An array of localized era strings.
@ -296,7 +295,8 @@ export function getLocaleMonthNames(
* *
* @publicApi * @publicApi
*/ */
export function getLocaleEraNames(locale: string, width: TranslationWidth): [string, string] { export function getLocaleEraNames(
locale: string, width: TranslationWidth): Readonly<[string, string]> {
const data = ɵfindLocaleData(locale); const data = ɵfindLocaleData(locale);
const erasData = <[string, string][]>data[ɵLocaleDataIndex.Eras]; const erasData = <[string, string][]>data[ɵLocaleDataIndex.Eras];
return getLastDefinedValue(erasData, width); return getLastDefinedValue(erasData, width);

View File

@ -13,7 +13,7 @@ import localeHe from '@angular/common/locales/he';
import localeZh from '@angular/common/locales/zh'; import localeZh from '@angular/common/locales/zh';
import {ɵregisterLocaleData, ɵunregisterLocaleData} from '@angular/core'; import {ɵregisterLocaleData, ɵunregisterLocaleData} from '@angular/core';
import {FormatWidth, getCurrencySymbol, getLocaleDateFormat, getLocaleDirection, getNumberOfCurrencyDigits} from '../../src/i18n/locale_data_api'; import {FormatWidth, FormStyle, getCurrencySymbol, getLocaleDateFormat, getLocaleDayNames, getLocaleDirection, getLocaleMonthNames, getNumberOfCurrencyDigits, TranslationWidth} from '../../src/i18n/locale_data_api';
{ {
describe('locale data api', () => { describe('locale data api', () => {
@ -71,5 +71,96 @@ import {FormatWidth, getCurrencySymbol, getLocaleDateFormat, getLocaleDirection,
expect(getLocaleDirection('en')).toEqual('ltr'); expect(getLocaleDirection('en')).toEqual('ltr');
}); });
}); });
describe('getLocaleDayNames', () => {
it('should return english short list of days', () => {
expect(
getLocaleDayNames('en-US', FormStyle.Format, TranslationWidth.Short),
)
.toEqual(['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']);
});
it('should return french short list of days', () => {
expect(
getLocaleDayNames('fr-CA', FormStyle.Format, TranslationWidth.Short),
)
.toEqual(['di', 'lu', 'ma', 'me', 'je', 've', 'sa']);
});
it('should return english wide list of days', () => {
expect(
getLocaleDayNames('en-US', FormStyle.Format, TranslationWidth.Wide),
)
.toEqual(
['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']);
});
it('should return french wide list of days', () => {
expect(
getLocaleDayNames('fr-CA', FormStyle.Format, TranslationWidth.Wide),
)
.toEqual(['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi']);
});
it('should return the full short list of days after manipulations', () => {
const days =
Array.from(getLocaleDayNames('en-US', FormStyle.Format, TranslationWidth.Short));
days.splice(2);
days.push('unexisting_day');
const newDays = getLocaleDayNames('en-US', FormStyle.Format, TranslationWidth.Short);
expect(newDays.length).toBe(7);
expect(newDays).toEqual(['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']);
});
});
describe('getLocaleMonthNames', () => {
it('should return english abbreviated list of month', () => {
expect(getLocaleMonthNames('en-US', FormStyle.Format, TranslationWidth.Abbreviated))
.toEqual([
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
]);
});
it('should return french abbreviated list of month', () => {
expect(getLocaleMonthNames('fr-CA', FormStyle.Format, TranslationWidth.Abbreviated))
.toEqual([
'janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.',
'nov.', 'déc.'
]);
});
it('should return english wide list of month', () => {
expect(getLocaleMonthNames('en-US', FormStyle.Format, TranslationWidth.Wide)).toEqual([
'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September',
'October', 'November', 'December'
]);
});
it('should return french wide list of month', () => {
expect(getLocaleMonthNames('fr-CA', FormStyle.Format, TranslationWidth.Wide)).toEqual([
'janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre',
'octobre', 'novembre', 'décembre'
]);
});
it('should return the full abbreviated list of month after manipulations', () => {
const month = Array.from(
getLocaleMonthNames('en-US', FormStyle.Format, TranslationWidth.Abbreviated));
month.splice(2);
month.push('unexisting_month');
const newMonth =
getLocaleMonthNames('en-US', FormStyle.Format, TranslationWidth.Abbreviated);
expect(newMonth.length).toBe(12);
expect(newMonth).toEqual(
['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']);
});
});
}); });
} }