-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
9b315a9
commit fe11bf5
Showing
7 changed files
with
170 additions
and
0 deletions.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import * as _ from 'radashi' | ||
|
||
describe('isClass', () => { | ||
bench('with class', () => { | ||
_.isClass(class CustomClass {}) | ||
}) | ||
|
||
bench('with non-class', () => { | ||
_.isClass({}) | ||
}) | ||
}) |
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,33 @@ | ||
--- | ||
title: isClass | ||
description: Determine if a value was declared with `class` syntax | ||
--- | ||
|
||
### Usage | ||
|
||
This function returns `true` if the provided value is a constructor declared with the ES6 `class` keyword. | ||
|
||
```ts | ||
import * as _ from 'radashi' | ||
|
||
class MyClass {} | ||
|
||
_.isClass(MyClass) // => true | ||
_.isClass(Error) // => false | ||
_.isClass(function OldClass() {}) // => false | ||
_.isClass('abc') // => false | ||
_.isClass({}) // => false | ||
_.isClass(undefined) // => false | ||
``` | ||
|
||
:::note | ||
|
||
Old school constructors (declared with the `function` keyword) will return `false`. | ||
|
||
Built-in class constructors (e.g. `Error`) will also return `false`, because they're created with native code, not the `class` keyword. | ||
|
||
::: | ||
|
||
### Popular use cases | ||
|
||
- Type guard would check for a function that must be called with the `new` operator |
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,34 @@ | ||
import { isFunction, type Class, type StrictExtract } from 'radashi' | ||
|
||
/** | ||
* Checks if the given value is a class. This function verifies | ||
* if the value was defined using the `class` syntax. Old school | ||
* classes (defined with constructor functions) will return false. | ||
* "Native classes" like `Error` will also return false. | ||
* | ||
* @see https://radashi.js.org/reference/typed/isClass | ||
* @example | ||
* ```ts | ||
* isClass(class CustomClass {}) // => true | ||
* isClass('abc') // => false | ||
* isClass({}) // => false | ||
* ``` | ||
*/ | ||
export function isClass<T>(value: T): value is ExtractClass<T> { | ||
return ( | ||
isFunction(value) && | ||
Function.prototype.toString.call(value).startsWith('class ') | ||
) | ||
} | ||
|
||
/** | ||
* Used by the `isClass` type guard. It handles type narrowing for | ||
* class constructors and even narrows `any` types. | ||
*/ | ||
export type ExtractClass<T> = [StrictExtract<T, Class>] extends [Class] | ||
? Extract<T, Class> | ||
: T extends any | ||
? Class<unknown[], unknown> extends T | ||
? Class<unknown[], unknown> | ||
: never | ||
: 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
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,45 @@ | ||
import type { Class } from 'radashi' | ||
import * as _ from 'radashi' | ||
import { expectTypeOf } from 'vitest' | ||
|
||
declare class Person { | ||
name: string | ||
} | ||
|
||
describe('isClass', () => { | ||
test('value is union containing a class type', () => { | ||
const value = {} as Person | typeof Person | ||
if (_.isClass(value)) { | ||
expectTypeOf(value).toEqualTypeOf<typeof Person>() | ||
expectTypeOf(new value()).toEqualTypeOf<Person>() | ||
} else { | ||
expectTypeOf(value).toEqualTypeOf<Person>() | ||
} | ||
}) | ||
test('value is unknown', () => { | ||
const value = {} as unknown | ||
if (_.isClass(value)) { | ||
expectTypeOf(value).toEqualTypeOf<Class<unknown[], unknown>>() | ||
expectTypeOf(new value()).toEqualTypeOf<unknown>() | ||
} else { | ||
expectTypeOf(value).toEqualTypeOf<unknown>() | ||
} | ||
}) | ||
test('value is any', () => { | ||
const value = {} as any | ||
if (_.isClass(value)) { | ||
expectTypeOf(value).toEqualTypeOf<Class<unknown[], unknown>>() | ||
expectTypeOf(new value()).toEqualTypeOf<unknown>() | ||
} else { | ||
expectTypeOf(value).toEqualTypeOf<any>() | ||
} | ||
}) | ||
test('value is string', () => { | ||
const value = {} as string | ||
if (_.isClass(value)) { | ||
expectTypeOf(value).toEqualTypeOf<never>() | ||
} else { | ||
expectTypeOf(value).toEqualTypeOf<string>() | ||
} | ||
}) | ||
}) |
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,39 @@ | ||
import * as _ from 'radashi' | ||
|
||
function OldSchoolClass(something: string) { | ||
// @ts-ignore | ||
this.something = something | ||
} | ||
OldSchoolClass.prototype.doSomething = function () { | ||
return `do ${this.something}` | ||
} | ||
|
||
describe('isClass', () => { | ||
test('returns false for non-Class values', () => { | ||
const fn = () => {} | ||
|
||
class MyFunction extends Function { | ||
toString() { | ||
return 'class instance of MyFunction' | ||
} | ||
} | ||
|
||
expect(_.isClass(OldSchoolClass)).toBeFalsy() | ||
expect(_.isClass(fn)).toBeFalsy() | ||
expect(_.isClass(undefined)).toBeFalsy() | ||
expect(_.isClass(null)).toBeFalsy() | ||
expect(_.isClass(false)).toBeFalsy() | ||
expect(_.isClass(() => {})).toBeFalsy() | ||
expect(_.isClass(async () => {})).toBeFalsy() | ||
expect(_.isClass(new MyFunction())).toBeFalsy() | ||
expect(_.isClass(Number.NaN)).toBeFalsy() | ||
expect(_.isClass([1, 2, 3])).toBeFalsy() | ||
expect(_.isClass({})).toBeFalsy() | ||
expect(_.isClass('abc')).toBeFalsy() | ||
expect(_.isClass(String('abc'))).toBeFalsy() | ||
}) | ||
test('returns true for class values', () => { | ||
expect(_.isClass(class CustomError extends Error {})).toBeTruthy() | ||
expect(_.isClass(class CustomClass {})).toBeTruthy() | ||
}) | ||
}) |