Skip to content

Commit

Permalink
[New] add types
Browse files Browse the repository at this point in the history
  • Loading branch information
ljharb committed Nov 23, 2024
1 parent 7b3d28b commit d4aa2db
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 7 deletions.
21 changes: 21 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import whichBoxedPrimitive from 'which-boxed-primitive';
import whichCollection from 'which-collection';
import whichTypedArray from 'which-typed-array';

type AsyncFunction<T = unknown> = (...args: unknown[]) => Promise<T>;

declare function whichBuiltinType<T>(value: Parameters<typeof whichCollection>[0]): ReturnType<typeof whichCollection>;
declare function whichBuiltinType<T>(value: Parameters<typeof whichTypedArray>[0]): ReturnType<typeof whichTypedArray>;
declare function whichBuiltinType<T>(value: ReadonlyArray<T>): 'Array';
declare function whichBuiltinType<T>(value: Date): 'Date';
declare function whichBuiltinType<T>(value: RegExp): 'RegExp';
declare function whichBuiltinType<T>(value: T extends object ? WeakRef<T> : never): 'WeakRef';
declare function whichBuiltinType<T>(value: FinalizationRegistry<T>): 'FinalizationRegistry';
declare function whichBuiltinType<T>(value: GeneratorFunction): 'GeneratorFunction';
declare function whichBuiltinType<T>(value: AsyncFunction<T>): 'AsyncFunction';
declare function whichBuiltinType<T>(value: Function): 'Function';
declare function whichBuiltinType<T>(value: Promise<T>): 'Promise';

declare function whichBuiltinType<T>(value: T): 'Object' | Exclude<ReturnType<typeof whichBoxedPrimitive>, null | undefined> | string;

export = whichBuiltinType;
10 changes: 8 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ var toStringTag = hasToStringTag && Symbol.toStringTag;

var $Object = Object;

/** @type {undefined | ((value: ThisParameterType<typeof Promise.prototype.then>, ...args: Parameters<typeof Promise.prototype.then>) => ReturnType<typeof Promise.prototype.then>)} */
var promiseThen = callBound('Promise.prototype.then', true);
/** @type {<T = unknown>(value: unknown) => value is Promise<T>} */
var isPromise = function isPromise(value) {
if (!value || typeof value !== 'object' || !promiseThen) {
return false;
Expand All @@ -29,8 +31,9 @@ var isPromise = function isPromise(value) {
return false;
};

/** @type {(builtinName: unknown) => boolean} */
var isKnownBuiltin = function isKnownBuiltin(builtinName) {
return builtinName
return !!builtinName
// primitives
&& builtinName !== 'BigInt'
&& builtinName !== 'Boolean'
Expand Down Expand Up @@ -74,6 +77,7 @@ var isKnownBuiltin = function isKnownBuiltin(builtinName) {
&& builtinName !== 'AsyncFunction';
};

/** @type {import('.')} */
module.exports = function whichBuiltinType(value) {
if (value == null) {
return value;
Expand Down Expand Up @@ -110,14 +114,16 @@ module.exports = function whichBuiltinType(value) {
if (isPromise(value)) {
return 'Promise';
}
// @ts-expect-error TS can't figure out that `value` is an `object` after the `which` check above
if (toStringTag && toStringTag in value) {
var tag = value[toStringTag];
if (isKnownBuiltin(tag)) {
return tag;
}
}
if (typeof value.constructor === 'function') {
var constructorName = name(value.constructor);
// eslint-disable-next-line no-extra-parens
var constructorName = name(/** @type {Parameters<name>[0]} */ (value.constructor));
if (isKnownBuiltin(constructorName)) {
return constructorName;
}
Expand Down
22 changes: 21 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"version": "1.1.4",
"description": "What is the type of this builtin JS value?",
"main": "index.js",
"types": "index.d.ts",
"exports": {
".": "./index.js",
"./package.json": "./package.json"
Expand All @@ -14,6 +15,7 @@
"prepublishOnly": "safe-publish-latest",
"prelint": "evalmd README.md",
"lint": "eslint --ext=js,mjs .",
"postlint": "tsc -P . && attw -P",
"pretest": "npm run lint",
"tests-only": "nyc tape 'test/**/*.js'",
"test": "npm run tests-only",
Expand Down Expand Up @@ -43,7 +45,24 @@
"node": ">= 0.4"
},
"devDependencies": {
"@arethetypeswrong/cli": "^0.17.0",
"@ljharb/eslint-config": "^21.1.1",
"@ljharb/tsconfig": "^0.2.0",
"@types/call-bind": "^1.0.5",
"@types/for-each": "^0.3.3",
"@types/function.prototype.name": "^1.1.3",
"@types/has-symbols": "^1.0.2",
"@types/is-async-function": "^2.0.3",
"@types/is-date-object": "^1.0.4",
"@types/is-generator-function": "^1.0.3",
"@types/is-regex": "^1.0.2",
"@types/is-weakref": "^1.0.0",
"@types/make-arrow-function": "^1.2.2",
"@types/make-async-function": "^1.0.2",
"@types/object-inspect": "^1.13.0",
"@types/object.assign": "^4.1.0",
"@types/tape": "^5.6.4",
"@types/which-boxed-primitive": "^1.0.3",
"auto-changelog": "^2.5.0",
"available-typed-arrays": "^1.0.7",
"encoding": "^0.1.13",
Expand All @@ -61,7 +80,8 @@
"object-inspect": "^1.13.3",
"object.assign": "^4.1.5",
"safe-publish-latest": "^2.0.0",
"tape": "^5.9.0"
"tape": "^5.9.0",
"typescript": "next"
},
"auto-changelog": {
"output": "CHANGELOG.md",
Expand Down
22 changes: 18 additions & 4 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@ if (typeof process !== 'undefined') {
test('nullish', function (t) {
t.equal(which(null), null, 'null is null');
t.equal(which(undefined), undefined, 'undefined is undefined');
// @ts-expect-error
t.equal(which(), undefined, 'absent is undefined');

t.end();
});

test('non-nullish', function (t) {
/** @constructor */
var F = function Foo() {};

var tests = {
Expand Down Expand Up @@ -69,11 +71,12 @@ test('non-nullish', function (t) {
].concat(arrows),
GeneratorFunction: generators,
AsyncFunction: asyncs,
Object: [
// eslint-disable-next-line no-extra-parens
Object: /** @type {object[]} */ ([
{},
{ constructor: null },
Math
],
]),
Symbol: hasSymbols ? [
Symbol.iterator,
Symbol(),
Expand Down Expand Up @@ -120,6 +123,7 @@ test('non-nullish', function (t) {
] : []
};
forEach(availableTypedArrays(), function (TypedArray) {
// @ts-expect-error not sure how to infer this as being spreaded into the above object literal
tests[TypedArray] = [
new global[TypedArray](0),
new global[TypedArray](2)
Expand All @@ -137,6 +141,7 @@ test('non-nullish', function (t) {
&& expected !== 'Foo' // not a builtin
) {
if (hasToStringTag) {
/** @type {{ [k in typeof Symbol.toStringTag]?: string }} */
var fakerTag = {};
fakerTag[Symbol.toStringTag] = expected;
t.equal(
Expand All @@ -146,15 +151,24 @@ test('non-nullish', function (t) {
);
}

var fakerConstructor = { constructor: global[expected] };
/** @typedef {Exclude<typeof expected, 'GeneratorFunction' | 'AsyncFunction' | 'Foo'>} GlobalKey */

var fakerConstructor = {
// eslint-disable-next-line no-extra-parens
constructor: global[/** @type {GlobalKey} */ (expected)] || tests[expected]
};
t.equal(
which(fakerConstructor),
'Object',
inspect(fakerConstructor) + ' lies and claims it is a ' + expected + ', but instead it is Object'
);

if (hasToStringTag) {
var fakerConstructorTag = { constructor: global[expected] };
/** @type {{ constructor: Function } & { [k in typeof Symbol.toStringTag]?: string }} */
var fakerConstructorTag = {
// eslint-disable-next-line no-extra-parens
constructor: global[/** @type {GlobalKey} */ (expected)] || tests[expected]
};
fakerConstructorTag[Symbol.toStringTag] = expected;
t.equal(
which(fakerConstructorTag),
Expand Down
9 changes: 9 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "@ljharb/tsconfig",
"compilerOptions": {
"target": "ES2021",
},
"exclude": [
"coverage"
]
}

0 comments on commit d4aa2db

Please sign in to comment.