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 97d3dc8
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 3 deletions.
4 changes: 3 additions & 1 deletion source/exact.d.ts
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 readonly 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>>>
// In TypeScript, Array is a subtype of ReadonlyArray, so always test Array before ReadonlyArray.
: ParameterType extends readonly unknown[] ? ReadonlyArray<Exact<ArrayElement<ParameterType>, ArrayElement<InputType>>>
: ParameterType extends object ? ExactObject<ParameterType, InputType>
: ParameterType;
129 changes: 127 additions & 2 deletions test-d/exact.ts
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,17 +230,104 @@ 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}}>;
type Type = Array<{x: string}> & Array<{z: number; d: {e: string; f: boolean}}>;
const fn = <T extends Exact<Type, T>>(args: T) => args;

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

{ // It should reject excess field
const input = [{
x: '',
z: 1,
d: {
e: 'test',
f: true, // Excess field
f: true,
g: '', // Excess field
},
}];
// @ts-expect-error
fn(input);
}

{ // It should reject missing field
const input = [{
x: '',
z: 1,
d: {
e: 'test',
// Missing f: boolean
},
}];
// @ts-expect-error
fn(input);
}

{ // It should reject missing field
const input = [{
x: '',
z: 1,
d: {
e: 'test',
f: '', // Type mismatch
},
}];
// @ts-expect-error
Expand Down

0 comments on commit 97d3dc8

Please sign in to comment.