diff --git a/package.json b/package.json index 9a4db6f42..b2b3bb34c 100644 --- a/package.json +++ b/package.json @@ -49,5 +49,10 @@ "@typescript-eslint/no-redeclare": "off", "@typescript-eslint/no-confusing-void-expression": "off" } + }, + "tsd": { + "compilerOptions": { + "noUnusedLocals": false + } } } diff --git a/source/except.d.ts b/source/except.d.ts index bf1dd4154..7abf3027c 100644 --- a/source/except.d.ts +++ b/source/except.d.ts @@ -29,9 +29,22 @@ type Filtered = Filter<'bar', 'foo'>; */ type Filter = IsEqual extends true ? never : (KeyType extends ExcludeType ? never : KeyType); +type ExceptOptions = { + /** + Disallow assigning non-specified properties. + + Note that any omitted properties in the resulting type will be present in autocomplete as `undefined`. + + @default false + */ + requireExactProps?: boolean; +}; + /** Create a type from an object type without certain keys. +We recommend setting the `requireExactProps` option to `true`. + 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. 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'; type Foo = { a: number; b: string; - c: boolean; }; -type FooWithoutA = Except; -//=> {b: string}; +type FooWithoutA = Except; +//=> {b: string} + +const fooWithoutA: FooWithoutA = {a: 1, b: '2'}; +//=> errors: 'a' does not exist in type '{ b: string; }' + +type FooWithoutB = Except; +//=> {a: number} & Partial> + +const fooWithoutB: FooWithoutB = {a: 1, b: '2'}; +//=> errors at 'b': Type 'string' is not assignable to type 'undefined'. ``` @category Object */ -export type Except = { +export type Except = { [KeyType in keyof ObjectType as Filter]: ObjectType[KeyType]; -}; +} & (Options['requireExactProps'] extends true + ? Partial> + : {}); diff --git a/test-d/except.ts b/test-d/except.ts index c166e52f8..b593137a8 100644 --- a/test-d/except.ts +++ b/test-d/except.ts @@ -1,8 +1,22 @@ -import {expectType} from 'tsd'; +import {expectType, expectError} from 'tsd'; import type {Except} from '../index'; declare const except: Except<{a: number; b: string}, 'b'>; expectType<{a: number}>(except); +expectError(except.b); + +const nonStrict = { + a: 1, + b: '2', +}; + +const nonStrictAssignment: typeof except = nonStrict; // No error + +declare const strictExcept: Except<{a: number; b: string}, 'b', {requireExactProps: true}>; + +expectError(() => { + const strictAssignment: typeof strictExcept = nonStrict; +}); // Generic properties type Example = { @@ -11,7 +25,7 @@ type Example = { bar: string; }; -const test: Except = {foo: 123, bar: 'asdf'}; +const test: Except = {foo: 123, bar: 'asdf'}; expectType(test.foo); // eslint-disable-next-line @typescript-eslint/dot-notation expectType(test['bar']);