Skip to content

Commit

Permalink
fix(Exact): support readonly array
Browse files Browse the repository at this point in the history
  • Loading branch information
zorji committed Jul 11, 2022
1 parent 72d6c6f commit e715dff
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 1 deletion.
4 changes: 3 additions & 1 deletion source/exact.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Extract the element of an array that also works for array union.
Return `never` if T is not an array.
It creates a type-safe way to access the element type of `unknown` type.
*/
type ArrayElement<T> = T extends unknown[] ? T[0] : never;
type ArrayElement<T> = T extends ReadonlyArray<unknown> ? T[0] : never;

/**
Extract the object field type if T is an object and K is a key of T, return `never` otherwise.
Expand Down Expand Up @@ -64,5 +64,7 @@ onlyAcceptNameImproved(invalidInput); // Compilation error
export type Exact<ParameterType, InputType> =
// Converting union of array to array of union. i.e. A[] & B[] => (A & B)[]
ParameterType extends unknown[] ? Array<Exact<ArrayElement<ParameterType>, ArrayElement<InputType>>>
// Converting union of array to array of union. i.e. A[] & B[] => (A & B)[]
: ParameterType extends ReadonlyArray<unknown> ? ReadonlyArray<Exact<ArrayElement<ParameterType>, ArrayElement<InputType>>>
: ParameterType extends object ? ExactObject<ParameterType, InputType>
: ParameterType;
86 changes: 86 additions & 0 deletions test-d/exact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,44 @@ import type {Exact} from '../index';
fn(input);
}

{ // It should reject readonly array
const input = [{code: ''}] as ReadonlyArray<{code: string}>;
// @ts-expect-error
fn(input);
}

{ // It should accept array with optional property
const input = [{code: '', name: ''}];
fn(input);
}

{ // It should reject array with excess property
const input = [{code: '', name: '', excessProperty: ''}];
// @ts-expect-error
fn(input);
}

{ // It should reject invalid type
const input = '';
// @ts-expect-error
fn(input);
}
}

{ // Spec - readonly array
type Type = ReadonlyArray<{code: string; name?: string}>;
const fn = <T extends Exact<Type, T>>(args: T) => args;

{ // It should accept array with required property only
const input = [{code: ''}];
fn(input);
}

{ // It should accept readonly array
const input = [{code: ''}] as ReadonlyArray<{code: string}>;
fn(input);
}

{ // It should accept array with optional property
const input = [{code: '', name: ''}];
fn(input);
Expand Down Expand Up @@ -192,6 +230,54 @@ import type {Exact} from '../index';
}
}

{ // Spec - union of readonly array + non readonly array
type Type = ReadonlyArray<{x: string}> & Array<{z: number}>;
const fn = <T extends Exact<Type, T>>(args: T) => args;

{ // It should accept valid input
const input = [{
x: '',
z: 1,
}];
fn(input);
}

{ // It should reject missing field
const input = [{
z: 1,
}];
// @ts-expect-error
fn(input);
}

{ // It should reject missing field
const input = [{
x: '',
}];
// @ts-expect-error
fn(input);
}

{ // It should reject incorrect type
const input = [{
x: 1,
z: 1,
}];
// @ts-expect-error
fn(input);
}

{ // It should reject excess field
const input = [{
x: '',
y: '',
z: 1,
}];
// @ts-expect-error
fn(input);
}
}

{ // Spec - union of array with nested fields
type Type = Array<{x: string}> & Array<{z: number; d: {e: string}}>;
const fn = <T extends Exact<Type, T>>(args: T) => args;
Expand Down

0 comments on commit e715dff

Please sign in to comment.