feat(common): stricter types for number pipes (#37447)

Make typing of number pipes stricter to catch some misuses (such as
passing an Observable or an array) at compile time.

BREAKING CHANGE:
The signatures of the number pipes now explicitly state which types are
accepted. This should only cause issues in corner cases, as any other
values would result in runtime exceptions.

PR Close #37447
This commit is contained in:
Andrea Canciani 2020-03-27 11:41:50 +01:00 committed by Alex Rickabaugh
parent daf8b7f100
commit 7b2aac97df
3 changed files with 76 additions and 14 deletions

View File

@ -13,7 +13,9 @@ export declare class CommonModule {
export declare class CurrencyPipe implements PipeTransform {
constructor(_locale: string, _defaultCurrencyCode?: string);
transform(value: any, currencyCode?: string, display?: 'code' | 'symbol' | 'symbol-narrow' | string | boolean, digitsInfo?: string, locale?: string): string | null;
transform(value: number | string, currencyCode?: string, display?: 'code' | 'symbol' | 'symbol-narrow' | string | boolean, digitsInfo?: string, locale?: string): string | null;
transform(value: null | undefined, currencyCode?: string, display?: 'code' | 'symbol' | 'symbol-narrow' | string | boolean, digitsInfo?: string, locale?: string): null;
transform(value: number | string | null | undefined, currencyCode?: string, display?: 'code' | 'symbol' | 'symbol-narrow' | string | boolean, digitsInfo?: string, locale?: string): string | null;
}
export declare class DatePipe implements PipeTransform {
@ -25,7 +27,9 @@ export declare class DatePipe implements PipeTransform {
export declare class DecimalPipe implements PipeTransform {
constructor(_locale: string);
transform(value: any, digitsInfo?: string, locale?: string): string | null;
transform(value: number | string, digitsInfo?: string, locale?: string): string | null;
transform(value: null | undefined, digitsInfo?: string, locale?: string): null;
transform(value: number | string | null | undefined, digitsInfo?: string, locale?: string): string | null;
}
export declare const DOCUMENT: InjectionToken<Document>;
@ -342,7 +346,9 @@ export declare class PathLocationStrategy extends LocationStrategy {
export declare class PercentPipe implements PipeTransform {
constructor(_locale: string);
transform(value: any, digitsInfo?: string, locale?: string): string | null;
transform(value: number | string, digitsInfo?: string, locale?: string): string | null;
transform(value: null | undefined, digitsInfo?: string, locale?: string): null;
transform(value: number | string | null | undefined, digitsInfo?: string, locale?: string): string | null;
}
export declare abstract class PlatformLocation {

View File

@ -67,8 +67,12 @@ export class DecimalPipe implements PipeTransform {
* When not supplied, uses the value of `LOCALE_ID`, which is `en-US` by default.
* See [Setting your app locale](guide/i18n#setting-up-the-locale-of-your-app).
*/
transform(value: any, digitsInfo?: string, locale?: string): string|null {
if (isEmpty(value)) return null;
transform(value: number|string, digitsInfo?: string, locale?: string): string|null;
transform(value: null|undefined, digitsInfo?: string, locale?: string): null;
transform(value: number|string|null|undefined, digitsInfo?: string, locale?: string): string|null;
transform(value: number|string|null|undefined, digitsInfo?: string, locale?: string): string
|null {
if (!isValue(value)) return null;
locale = locale || this._locale;
@ -121,8 +125,12 @@ export class PercentPipe implements PipeTransform {
* When not supplied, uses the value of `LOCALE_ID`, which is `en-US` by default.
* See [Setting your app locale](guide/i18n#setting-up-the-locale-of-your-app).
*/
transform(value: any, digitsInfo?: string, locale?: string): string|null {
if (isEmpty(value)) return null;
transform(value: number|string, digitsInfo?: string, locale?: string): string|null;
transform(value: null|undefined, digitsInfo?: string, locale?: string): null;
transform(value: number|string|null|undefined, digitsInfo?: string, locale?: string): string|null;
transform(value: number|string|null|undefined, digitsInfo?: string, locale?: string): string
|null {
if (!isValue(value)) return null;
locale = locale || this._locale;
try {
const num = strToNumber(value);
@ -213,10 +221,22 @@ export class CurrencyPipe implements PipeTransform {
* See [Setting your app locale](guide/i18n#setting-up-the-locale-of-your-app).
*/
transform(
value: any, currencyCode?: string,
value: number|string, currencyCode?: string,
display?: 'code'|'symbol'|'symbol-narrow'|string|boolean, digitsInfo?: string,
locale?: string): string|null;
transform(
value: null|undefined, currencyCode?: string,
display?: 'code'|'symbol'|'symbol-narrow'|string|boolean, digitsInfo?: string,
locale?: string): null;
transform(
value: number|string|null|undefined, currencyCode?: string,
display?: 'code'|'symbol'|'symbol-narrow'|string|boolean, digitsInfo?: string,
locale?: string): string|null;
transform(
value: number|string|null|undefined, currencyCode?: string,
display: 'code'|'symbol'|'symbol-narrow'|string|boolean = 'symbol', digitsInfo?: string,
locale?: string): string|null {
if (isEmpty(value)) return null;
if (!isValue(value)) return null;
locale = locale || this._locale;
@ -246,8 +266,8 @@ export class CurrencyPipe implements PipeTransform {
}
}
function isEmpty(value: any): boolean {
return value == null || value === '' || value !== value;
function isValue(value: number|string|null|undefined): value is number|string {
return !(value == null || value === '' || value !== value);
}
/**

View File

@ -50,8 +50,20 @@ import {beforeEach, describe, expect, it} from '@angular/core/testing/src/testin
expect(pipe.transform('1.1234')).toEqual('1.123');
});
it('should return null for NaN', () => {
expect(pipe.transform(Number.NaN)).toEqual(null);
});
it('should return null for null', () => {
expect(pipe.transform(null)).toEqual(null);
});
it('should return null for undefined', () => {
expect(pipe.transform(undefined)).toEqual(null);
});
it('should not support other objects', () => {
expect(() => pipe.transform({}))
expect(() => pipe.transform({} as any))
.toThrowError(
`InvalidPipeArgument: '[object Object] is not a number' for pipe 'DecimalPipe'`);
expect(() => pipe.transform('123abc'))
@ -82,8 +94,20 @@ import {beforeEach, describe, expect, it} from '@angular/core/testing/src/testin
expect(pipe.transform(12.3456, '0.0-10')).toEqual('1,234.56%');
});
it('should return null for NaN', () => {
expect(pipe.transform(Number.NaN)).toEqual(null);
});
it('should return null for null', () => {
expect(pipe.transform(null)).toEqual(null);
});
it('should return null for undefined', () => {
expect(pipe.transform(undefined)).toEqual(null);
});
it('should not support other objects', () => {
expect(() => pipe.transform({}))
expect(() => pipe.transform({} as any))
.toThrowError(
`InvalidPipeArgument: '[object Object] is not a number' for pipe 'PercentPipe'`);
});
@ -125,8 +149,20 @@ import {beforeEach, describe, expect, it} from '@angular/core/testing/src/testin
expect(pipe.transform(5.1234, 'USD', 'Custom name')).toEqual('Custom name5.12');
});
it('should return null for NaN', () => {
expect(pipe.transform(Number.NaN)).toEqual(null);
});
it('should return null for null', () => {
expect(pipe.transform(null)).toEqual(null);
});
it('should return null for undefined', () => {
expect(pipe.transform(undefined)).toEqual(null);
});
it('should not support other objects', () => {
expect(() => pipe.transform({}))
expect(() => pipe.transform({} as any))
.toThrowError(
`InvalidPipeArgument: '[object Object] is not a number' for pipe 'CurrencyPipe'`);
});