From 3e7b1d7338ed9270b3b737bb340e7eda38850e60 Mon Sep 17 00:00:00 2001 From: BaoLong Wang Date: Thu, 15 Aug 2024 00:06:59 +0800 Subject: [PATCH] Add `safeCastTo()` #61 --- readme.md | 1 + source/index.ts | 1 + source/safe-cast-to.ts | 31 +++++++++++++++++++++++++++++++ test/safe-cast-to.ts | 38 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 71 insertions(+) create mode 100644 source/safe-cast-to.ts create mode 100644 test/safe-cast-to.ts diff --git a/readme.md b/readme.md index 4adcf0c..e4e4280 100644 --- a/readme.md +++ b/readme.md @@ -24,6 +24,7 @@ import {isDefined} from 'ts-extras'; **General** - [`asWritable`](source/as-writable.ts) - Cast the given value to be [`Writable`](https://github.com/sindresorhus/type-fest/blob/main/source/writable.d.ts). +- [`safeCastTo`](source/safe-cast-to.ts) - Cast a value to the given type safely. **Type guard** diff --git a/source/index.ts b/source/index.ts index e1bf147..2c97589 100644 --- a/source/index.ts +++ b/source/index.ts @@ -1,3 +1,4 @@ +export {safeCastTo} from './safe-cast-to.js'; export {arrayIncludes} from './array-includes.js'; export {asWritable} from './as-writable.js'; export {assertError} from './assert-error.js'; diff --git a/source/safe-cast-to.ts b/source/safe-cast-to.ts new file mode 100644 index 0000000..9f66108 --- /dev/null +++ b/source/safe-cast-to.ts @@ -0,0 +1,31 @@ +/** +Cast a value to the given type safely. + +This is useful since the `as` keyword allows you to convert between two unrelated types, which is type-unsafe. For example, converting a number to a string type. + +However, this function only allows you to cast a given value to a type that is compatible with it, avoiding the potential risk of using "as" and not breaking the type safety of your code. + +@example +``` +type Foo = { + a: string; + b?: number; +}; + +declare const possibleUndefined: Foo | undefined; + +const foo = possibleUndefined ?? safeCastTo>({}); +console.log(foo.a ?? '', foo.b ?? 0); + +const bar = possibleUndefined ?? {}; +// @ts-expect-error +console.log(bar.a ?? '', bar.b ?? 0); +// ^^^ Property 'a' does not exist on type '{}'.(2339) +// ^^^ Property 'b' does not exist on type '{}'.(2339) +``` + +@category General +*/ +export function safeCastTo(value: T): T { + return value; +} diff --git a/test/safe-cast-to.ts b/test/safe-cast-to.ts new file mode 100644 index 0000000..5ac0886 --- /dev/null +++ b/test/safe-cast-to.ts @@ -0,0 +1,38 @@ +/* eslint-disable @typescript-eslint/ban-types */ +import test from 'ava'; +import {expectTypeOf} from 'expect-type'; +import {safeCastTo} from '../source/index.js'; + +test('safeCastTo()', t => { + type Foo = { + a: string; + b?: number; + }; + + const EmptyObject = {}; + const foo: Foo = { + a: '', + b: 0, + }; + + t.is(EmptyObject, safeCastTo(EmptyObject)); + t.is(foo, safeCastTo(foo)); + + expectTypeOf({}).toEqualTypeOf<{}>(); + expectTypeOf(safeCastTo({})).toEqualTypeOf<{}>(); + expectTypeOf({}).not.toEqualTypeOf>(); + expectTypeOf(safeCastTo({})).not.toEqualTypeOf>(); + expectTypeOf(safeCastTo>({})).toEqualTypeOf>(); + expectTypeOf(safeCastTo>({}).a).toEqualTypeOf(); + expectTypeOf(safeCastTo>({}).b).toEqualTypeOf(); + + expectTypeOf(foo).toEqualTypeOf(); + expectTypeOf(safeCastTo(foo)).toEqualTypeOf(); + expectTypeOf(safeCastTo>(foo)).not.toEqualTypeOf(); + expectTypeOf(safeCastTo>(foo)).toEqualTypeOf>(); + + // @ts-expect-error + safeCastTo({}); + // @ts-expect-error + safeCastTo>(foo); +});