Skip to content

Commit c5743c9

Browse files
orimiles5sindresorhustommy-mitchell
authoredMar 23, 2023
Except: Add requireExactProps option (#560)
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com> Co-authored-by: Tommy <tmitchell7@uh.edu>
1 parent 18f8a11 commit c5743c9

File tree

3 files changed

+49
-7
lines changed

3 files changed

+49
-7
lines changed
 

‎package.json

+5
Original file line numberDiff line numberDiff line change
@@ -49,5 +49,10 @@
4949
"@typescript-eslint/no-redeclare": "off",
5050
"@typescript-eslint/no-confusing-void-expression": "off"
5151
}
52+
},
53+
"tsd": {
54+
"compilerOptions": {
55+
"noUnusedLocals": false
56+
}
5257
}
5358
}

‎source/except.d.ts

+28-5
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,22 @@ type Filtered = Filter<'bar', 'foo'>;
2929
*/
3030
type Filter<KeyType, ExcludeType> = IsEqual<KeyType, ExcludeType> extends true ? never : (KeyType extends ExcludeType ? never : KeyType);
3131

32+
type ExceptOptions = {
33+
/**
34+
Disallow assigning non-specified properties.
35+
36+
Note that any omitted properties in the resulting type will be present in autocomplete as `undefined`.
37+
38+
@default false
39+
*/
40+
requireExactProps?: boolean;
41+
};
42+
3243
/**
3344
Create a type from an object type without certain keys.
3445
46+
We recommend setting the `requireExactProps` option to `true`.
47+
3548
This type is a stricter version of [`Omit`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-5.html#the-omit-helper-type). The `Omit` type does not restrict the omitted keys to be keys present on the given type, while `Except` does. The benefits of a stricter type are avoiding typos and allowing the compiler to pick up on rename refactors automatically.
3649
3750
This type was proposed to the TypeScript team, which declined it, saying they prefer that libraries implement stricter versions of the built-in types ([microsoft/TypeScript#30825](https://github.com/microsoft/TypeScript/issues/30825#issuecomment-523668235)).
@@ -43,15 +56,25 @@ import type {Except} from 'type-fest';
4356
type Foo = {
4457
a: number;
4558
b: string;
46-
c: boolean;
4759
};
4860
49-
type FooWithoutA = Except<Foo, 'a' | 'c'>;
50-
//=> {b: string};
61+
type FooWithoutA = Except<Foo, 'a'>;
62+
//=> {b: string}
63+
64+
const fooWithoutA: FooWithoutA = {a: 1, b: '2'};
65+
//=> errors: 'a' does not exist in type '{ b: string; }'
66+
67+
type FooWithoutB = Except<Foo, 'b', {requireExactProps: true}>;
68+
//=> {a: number} & Partial<Record<"b", never>>
69+
70+
const fooWithoutB: FooWithoutB = {a: 1, b: '2'};
71+
//=> errors at 'b': Type 'string' is not assignable to type 'undefined'.
5172
```
5273
5374
@category Object
5475
*/
55-
export type Except<ObjectType, KeysType extends keyof ObjectType> = {
76+
export type Except<ObjectType, KeysType extends keyof ObjectType, Options extends ExceptOptions = {requireExactProps: false}> = {
5677
[KeyType in keyof ObjectType as Filter<KeyType, KeysType>]: ObjectType[KeyType];
57-
};
78+
} & (Options['requireExactProps'] extends true
79+
? Partial<Record<KeysType, never>>
80+
: {});

‎test-d/except.ts

+16-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,22 @@
1-
import {expectType} from 'tsd';
1+
import {expectType, expectError} from 'tsd';
22
import type {Except} from '../index';
33

44
declare const except: Except<{a: number; b: string}, 'b'>;
55
expectType<{a: number}>(except);
6+
expectError(except.b);
7+
8+
const nonStrict = {
9+
a: 1,
10+
b: '2',
11+
};
12+
13+
const nonStrictAssignment: typeof except = nonStrict; // No error
14+
15+
declare const strictExcept: Except<{a: number; b: string}, 'b', {requireExactProps: true}>;
16+
17+
expectError(() => {
18+
const strictAssignment: typeof strictExcept = nonStrict;
19+
});
620

721
// Generic properties
822
type Example = {
@@ -11,7 +25,7 @@ type Example = {
1125
bar: string;
1226
};
1327

14-
const test: Except<Example, 'bar'> = {foo: 123, bar: 'asdf'};
28+
const test: Except<Example, 'bar', {requireExactProps: false}> = {foo: 123, bar: 'asdf'};
1529
expectType<number>(test.foo);
1630
// eslint-disable-next-line @typescript-eslint/dot-notation
1731
expectType<unknown>(test['bar']);

0 commit comments

Comments
 (0)
Please sign in to comment.