-
-
Notifications
You must be signed in to change notification settings - Fork 571
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adds type constructors Patch
and ReplaceDeep
#648
Closed
ahrjarrett
wants to merge
38
commits into
sindresorhus:main
from
ahrjarrett:@ahrjarrett/deep-undefined-to-null
Closed
Changes from 8 commits
Commits
Show all changes
38 commits
Select commit
Hold shift + click to select a range
041d6ec
test: write type-level tests for `DeepUndefinedToNull`
ahrjarrett b6a5487
feat: adds `DeepUndefinedToNull` function
ahrjarrett 9311bd9
chore: adds `DeepUndefinedToNull` to toplevel readme
ahrjarrett 7d7a540
chore: fixes formatting
ahrjarrett 83f6ab4
feat: `DeepUndefinedToNull` supports unions
ahrjarrett 1efc09f
chore: formatting stuff
ahrjarrett d502f5e
fix: adds `declare` to `Any` namespace
ahrjarrett 2b1f903
fix: revert fomatting
ahrjarrett ae125e5
makes requested changes, changes the default replacement from `null` …
ahrjarrett fb1a959
renames file from `deep-undefined-to-null.d.ts` to `patch.d.ts`
ahrjarrett 47f9dd5
finishes renaming `DeepUndefinedToNull` to `Patch`, updates tests to …
ahrjarrett 208f005
renames test file
ahrjarrett 110f996
formats PR
ahrjarrett 4ae8d67
Merge branch 'main' into @ahrjarrett/deep-undefined-to-null
ahrjarrett 1d0683a
chore: renames `Patch` to `ReplaceOptionalDeep`
ahrjarrett 3a1e06c
chore: updates readme, formats test file
ahrjarrett b2a128f
Merge branch '@ahrjarrett/deep-undefined-to-null' of https://github.c…
ahrjarrett 4e4d552
chore: formats test file
ahrjarrett 9b97f93
Merge branch 'main' into @ahrjarrett/deep-undefined-to-null
sindresorhus 4a1eb46
implements first iteration of `Patch`
ahrjarrett 8910507
moves `patch` into own folder for better types at the call site; adds…
ahrjarrett 5deeae2
adds `Patch` to index.d.ts
ahrjarrett 0587b30
Merge branch 'main' of https://github.com/sindresorhus/type-fest into…
ahrjarrett 267d997
Merge branch '@ahrjarrett/deep-undefined-to-null' of https://github.c…
ahrjarrett 0e210d3
adds `ReplaceDeep` iteration
ahrjarrett c97f517
exports `ReplaceDeep` from barrel file, cleanup in `patch`, `replace-…
ahrjarrett 2a593da
adds `patch` test to make sure replacing an optional prop with a type…
ahrjarrett 7f7ffa1
adds test to `replace-deep` that boxes the input type -- because the …
ahrjarrett 5856a2f
Merge branch 'main' of https://github.com/sindresorhus/type-fest into…
ahrjarrett 40e3dce
clean up the api to be more explicit
ahrjarrett 9f5d941
removes union check, `isUnion` helper
ahrjarrett 0624d85
removes replace-optional-deep
ahrjarrett 286ec90
fixes patch test
ahrjarrett adc111d
updates readme and removes export for `ReplaceOptinalDeep`
ahrjarrett d90faf9
adds `Patch` examples
ahrjarrett 28de8d2
chore: linting errors in `patch/index.d.ts`
ahrjarrett a2b1723
removes inline test
ahrjarrett be15e00
autofix `replace-deep.d.ts` and `replace-deep.ts`
ahrjarrett File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
/** | ||
* Type function that accepts a record, recursively removes all optional property modifiers, and in those properties' values replaces `undefined` with `null`. | ||
* | ||
* - Note that this replacement also occurs in all union types (this function is distributive). | ||
* - Also note that in non-optional union values that include `undefined`, `undefined` is not removed (undefined is only replaced for optional keys). | ||
* | ||
* Optionally, providing the second parameter to `DeepUndefinedToNull` lets you specify the type to replace `undefined` with (otherwise it defaults to null). | ||
* | ||
* Use-cases: | ||
* | ||
* - JSON, for example, does not accept `undefined` as a value. If you need to send an object containing undefined over the wire, without a function | ||
* like `DeepUndefinedToNull`, you'd need to write that type by hand. | ||
* | ||
* - Since JavaScript runtimes will implicitly insert `undefined` in the absence of a value, using `undefined` can create ambiguity (is this value | ||
* undefined because someone forgot to add a value, or because `undefined` was used specifically?). | ||
* | ||
* @example | ||
* import type {DeepUndefinedToNull} from 'type-fest'; | ||
* | ||
* type TypeWithOptionalProps = {a?: 1; b: 2; c: 3 | undefined; d: {e?: 3}}; | ||
* type TypeWithoutOptionals = DeepUndefinedToNull<TypeWithOptionalProps>; | ||
* // ^? {a: 1 | null; b: 2; c: 3 | undefined; d: {e: 3 | null}} | ||
* | ||
* type NestedUnionWithOptionalProps = {a?: {b?: 1} | {b?: 2}}; | ||
* type NestedUnionWithoutOptionals = DeepUndefinedToNull<NestedUnionWithOptionalProps>; | ||
* // ^? {a: null | {b: 1 | null} | {b: 2 | null}} | ||
* | ||
* type TypeWithCustomReplacement = DeepUndefinedToNull<TypeWithOptionalProps, "yolo">; | ||
* // ^? {a: 1 | "yolo"; b: 2; c: 3 | undefined; d: {e: 3 | "yolo"}} | ||
* | ||
* @category Type | ||
* @category Object | ||
*/ | ||
export type DeepUndefinedToNull< | ||
Type, | ||
Replace = null, | ||
> = DeepReplace<DeepUndefinedToPlaceholder<Type, Replace>, Placeholder, undefined>; | ||
|
||
declare namespace Any { | ||
export type record = Record<symbol, any>; | ||
export type array = readonly any[]; | ||
} | ||
|
||
/** @internal */ | ||
type Placeholder = typeof Placeholder; | ||
/** @internal */ | ||
declare const Placeholder: unique symbol; | ||
|
||
/** | ||
* This could probably be separated into its own module and exported | ||
*/ | ||
type DeepReplace< | ||
Type, | ||
Find, | ||
Replace, | ||
> = Type extends Type | ||
? Type extends Find | ||
? Replace | ||
: Type extends Any.record | ||
? {[Key in keyof Type]: DeepReplace<Type[Key], Find, Replace>} | ||
: Type extends Any.array | ||
? {[Ix in keyof Type]: DeepReplace<Type[Ix], Find, Replace>} | ||
: Type | ||
: never; | ||
|
||
type ReplaceUnionMember< | ||
Union, | ||
Find, | ||
Replace, | ||
> = Union extends Union | ||
? Union extends Find | ||
? Replace | ||
: Union | ||
: never; | ||
|
||
/** @internal */ | ||
type DeepUndefinedToPlaceholder< | ||
Type, | ||
Replace, | ||
> | ||
/** | ||
* `Type extends Type` distributes `Type`, so for the rest of this | ||
* function `Type` refers to the individual member of the union, | ||
* rather than the union itself | ||
*/ | ||
= Type extends Type | ||
/** | ||
* Does this member of the union extend `undefined`? Replace it in | ||
* the union it belongs to as `Placeholder` | ||
*/ | ||
? Type extends undefined | ||
? Placeholder | ||
/** | ||
* Is `Type` a record? | ||
*/ | ||
: Type extends Record<symbol, any> | ||
? { | ||
/** | ||
* Make the properties of `Type` required | ||
*/ | ||
[Key in keyof Type]-?: | ||
/** | ||
* Traverse each value recursively | ||
*/ | ||
DeepUndefinedToPlaceholder< | ||
/** | ||
* Is `Key` optional in `Type`? | ||
*/ | ||
{} extends Pick<Type, Key> | ||
/** | ||
* ...if yes, replace `undefined` with `null` in `Type[Key]` union | ||
*/ | ||
? ReplaceUnionMember<Type[Key], undefined, Replace> | ||
/** | ||
* ...otherwise just use `Type[Key]` when recursing | ||
*/ | ||
: Type[Key] | ||
, Replace | ||
> | ||
} | ||
: Type | ||
: never; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import {expectType} from 'tsd'; | ||
import type {DeepUndefinedToNull} from '../index'; | ||
|
||
type In1 = {a: string; b?: boolean}; | ||
type Out1 = {a: string; b: boolean | null}; | ||
|
||
type In2 = {a?: string; b?: boolean}; | ||
type Out2 = {a: string | null; b: boolean | null}; | ||
|
||
type In3 = {a: string; b: boolean}; | ||
type Out3 = {a: string; b: boolean}; | ||
|
||
type In4 = {a: undefined}; | ||
type Out4 = In4; | ||
|
||
type In5 = { | ||
a: 1; | ||
b?: 2; | ||
c?: 3 | undefined; | ||
d?: 4 | null; | ||
e?: 5 | undefined | null; | ||
f: 6 | undefined; | ||
g?: {}; | ||
h: { | ||
i: 7; | ||
j?: 7; | ||
k?: { | ||
l?: 8; | ||
m: 9; | ||
n?: 10 | undefined; | ||
o?: 11 | null; | ||
p?: 12 | undefined | null; | ||
q: 13 | undefined; | ||
}; | ||
}; | ||
}; | ||
|
||
type Out5 = { | ||
a: 1; | ||
b: | null | 2; | ||
c: | null | 3; | ||
d: | null | 4; | ||
e: | null | 5; | ||
f: | undefined | 6; | ||
g: | null | {}; | ||
h: { | ||
i: 7; | ||
j: | null | 7; | ||
k: | ||
| null | ||
| { | ||
l: | null | 8; | ||
m: 9; | ||
n: | null | 10; | ||
o: | null | 11; | ||
p: | null | 12; | ||
q: | undefined | 13; | ||
}; | ||
}; | ||
}; | ||
|
||
type In6 = { | ||
a?: | ||
| 1 | ||
| {b: 2; c?: 3; d: undefined | readonly undefined[] | {e?: {a?: 1} | {a?: 2}}}; | ||
}; | ||
|
||
type Out6 = { | ||
a: 1 | { | ||
b: 2; | ||
c: 3 | null; | ||
d: readonly undefined[] | { | ||
e: { | ||
a: 1 | null; | ||
} | { | ||
a: 2 | null; | ||
} | null; | ||
} | undefined; | ||
} | null; | ||
}; | ||
|
||
type In7 = { | ||
a?: 1; | ||
}; | ||
|
||
type Out7 = { | ||
a: | 0 | 1; | ||
}; | ||
|
||
declare const test1: DeepUndefinedToNull<In1>; | ||
declare const test2: DeepUndefinedToNull<In2>; | ||
declare const test3: DeepUndefinedToNull<In3>; | ||
declare const test4: DeepUndefinedToNull<In4>; | ||
declare const test5: DeepUndefinedToNull<In5>; | ||
declare const test6: DeepUndefinedToNull<In6>; | ||
declare const test7: DeepUndefinedToNull<In7, 0>; | ||
|
||
expectType<Out1>(test1); | ||
expectType<Out2>(test2); | ||
expectType<Out3>(test3); | ||
expectType<Out4>(test4); | ||
expectType<Out5>(test5); | ||
expectType<Out6>(test6); | ||
expectType<Out7>(test7); |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't use the
*
prefix for doc comments. See other types here.