Skip to content

Commit

Permalink
fix(common): mark locale data arrays as readonly (angular#30397)
Browse files Browse the repository at this point in the history
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 angular#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 angular#30397
  • Loading branch information
Artem Halas authored and AndrewKushnir committed Sep 10, 2020
1 parent 2d52c80 commit 6acea54
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 10 deletions.
8 changes: 4 additions & 4 deletions goldens/public-api/common/common.d.ts
Expand Up @@ -61,13 +61,13 @@ export declare function getLocaleDateFormat(locale: string, width: FormatWidth):

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 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])[];

Expand All @@ -77,7 +77,7 @@ export declare function getLocaleFirstDayOfWeek(locale: string): WeekDay;

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;

Expand Down
10 changes: 5 additions & 5 deletions packages/common/src/i18n/locale_data_api.ts
Expand Up @@ -233,7 +233,7 @@ export function getLocaleId(locale: string): string {
* @publicApi
*/
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 amPmData = <[string, string][][]>[
data[ɵLocaleDataIndex.DayPeriodsFormat], data[ɵLocaleDataIndex.DayPeriodsStandalone]
Expand All @@ -255,7 +255,7 @@ export function getLocaleDayPeriods(
* @publicApi
*/
export function getLocaleDayNames(
locale: string, formStyle: FormStyle, width: TranslationWidth): string[] {
locale: string, formStyle: FormStyle, width: TranslationWidth): ReadonlyArray<string> {
const data = ɵfindLocaleData(locale);
const daysData =
<string[][][]>[data[ɵLocaleDataIndex.DaysFormat], data[ɵLocaleDataIndex.DaysStandalone]];
Expand All @@ -276,7 +276,7 @@ export function getLocaleDayNames(
* @publicApi
*/
export function getLocaleMonthNames(
locale: string, formStyle: FormStyle, width: TranslationWidth): string[] {
locale: string, formStyle: FormStyle, width: TranslationWidth): ReadonlyArray<string> {
const data = ɵfindLocaleData(locale);
const monthsData =
<string[][][]>[data[ɵLocaleDataIndex.MonthsFormat], data[ɵLocaleDataIndex.MonthsStandalone]];
Expand All @@ -287,7 +287,6 @@ export function getLocaleMonthNames(
/**
* Retrieves Gregorian-calendar eras for the given locale.
* @param locale A locale code for the locale format rules to use.
* @param formStyle The required grammatical form.
* @param width The required character width.
* @returns An array of localized era strings.
Expand All @@ -296,7 +295,8 @@ export function getLocaleMonthNames(
*
* @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 erasData = <[string, string][]>data[ɵLocaleDataIndex.Eras];
return getLastDefinedValue(erasData, width);
Expand Down
93 changes: 92 additions & 1 deletion packages/common/test/i18n/locale_data_api_spec.ts
Expand Up @@ -13,7 +13,7 @@ import localeHe from '@angular/common/locales/he';
import localeZh from '@angular/common/locales/zh';
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', () => {
Expand Down Expand Up @@ -71,5 +71,96 @@ import {FormatWidth, getCurrencySymbol, getLocaleDateFormat, getLocaleDirection,
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']);
});
});
});
}

0 comments on commit 6acea54

Please sign in to comment.