Skip to content
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

add TypeScript support #28

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .verb.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,20 @@ console.log(typeOf(new WeakMap()));
console.log(typeOf(new WeakSet()));
//=> 'object'
```

## TypeScript support

All types resolve correctly with 2 exceptions where typescript cannot yet determine types:

**Iterators**
```typescript
// For Array, Map, String, and Set iterators the return type will be "stringiterator" | "setiterator" | "arrayiterator" | "mapiterator"
kindOf(""[Symbol.iterator]()); // "stringiterator" | "setiterator" | "arrayiterator" | "mapiterator"
```

**Object.create(...)**
```typescript
// You will need to type Object.create(...) yourself as TypeScript types this as any
kindOf(Object.create(null)); // "object"
kindOf(Object.create(null) as object); // "object"
```
60 changes: 60 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
interface DateLike {
toDateString: Function;
getDate: Function;
setDate: Function;
};
interface RegExpLike {
flags: string;
ignoreCase: boolean;
multiline: boolean;
global: boolean;
};
interface GeneratorLike {
throw: Function;
return: Function;
next: Function;
};
interface ErrorLike {
message: string;
constructor: {
stackTraceLimit: number;
};
};
declare type KindOf<T> =
T extends undefined ? "undefined" :
T extends null ? "null" :
T extends boolean | Boolean ? "boolean" :
T extends string | String ? "string" :
T extends number | Number ? "number" :
T extends symbol ? "symbol" :
T extends IterableIterator<any> ? "mapiterator" | "setiterator" | "stringiterator" | "arrayiterator" :
T extends (...args: any[]) => IterableIterator<any> ? "generatorfunction" | "function" :
T extends Function ? "function" :
T extends Array<any> ? "array" :
T extends Date | DateLike ? "date" :
T extends Buffer ? "buffer" :
T extends Error | ErrorLike ? "error" :
T extends RegExp | RegExpLike ? "regexp" :
T extends Promise<any> ? "promise" :
T extends Map<any, any> ? 'map' :
T extends Set<any> ? 'set' :
T extends WeakMap<any, any> ? 'weakmap' :
T extends WeakSet<any> ? 'weakset' :
T extends Int8Array ? 'int8array' :
T extends Uint8Array ? 'uint8array' :
T extends Uint8ClampedArray ? 'uint8clampedarray' :
T extends Int16Array ? 'int16array' :
T extends Uint16Array ? 'uint16array' :
T extends Int32Array ? 'int32array' :
T extends Uint32Array ? 'uint32array' :
T extends Float32Array ? 'float32array' :
T extends Float64Array ? 'float64array' :
T extends IArguments ? 'arguments' :
T extends GeneratorLike ? 'generator' :
T extends NodeJS.Global ? 'global' :
T extends Window ? 'window' :
T extends object ? 'object' :
string;
declare function kindOf(): KindOf<undefined>;
declare function kindOf<T>(v: T): KindOf<T>;
export default kindOf;
11 changes: 8 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,34 @@
"laggingreflex (https://github.com/laggingreflex)",
"Miguel Mota (https://miguelmota.com)",
"Peter deHaan (http://about.me/peterdehaan)",
"tunnckoCore (https://i.am.charlike.online)"
"tunnckoCore (https://i.am.charlike.online)",
"Stephen Murphy (https://github.com/Stephen-Murphy)"
],
"repository": "jonschlinkert/kind-of",
"bugs": {
"url": "https://github.com/jonschlinkert/kind-of/issues"
},
"license": "MIT",
"files": [
"index.js"
"index.js",
"index.d.ts"
],
"main": "index.js",
"engines": {
"node": ">=0.10.0"
},
"scripts": {
"test": "mocha",
"prepublish": "browserify -o browser.js -e index.js -s index --bare"
"prepublish": "browserify -o browser.js -e index.js -s index --bare",
"test-ts": "tsc && node ./test/typescript"
},
"devDependencies": {
"@types/node": "^10.12.9",
"benchmarked": "^2.0.0",
"browserify": "^14.4.0",
"gulp-format-md": "^1.0.0",
"mocha": "^4.0.1",
"typescript": "^3.5.2",
"write": "^1.0.3"
},
"keywords": [
Expand Down
117 changes: 117 additions & 0 deletions test/typescript/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import kindOf from "../../";

// View this file in a TypeScript environment or compile it to check for errors (npm run test-ts)
// As a sanity check, you can also execute the compiled js file in node to ensure types match functionality

// sanity check: next line should yield TS[2367] IDE and compile-time error when uncommented
// console.assert(kindOf(1) === "undefined")

/* ### Caveats and Edge Cases

#### Iterators
TypeScript determines the iterator type by the values returned in the iterator as opposed to the target object being iterated over, whereas `kind-of` returns the iterator target type as well
The (TypeScript) return type of `kindOf((new Map)[Symbol.iterator]())` (and any other iterator regardless of target) will be `"mapiterator" | "setiterator" | "stringiterator" | "arrayiterator"`.
To test for an iterator regardless of source you would simply use `kindOf(foo).endsWith("iterator")`.

#### Functions/GeneratorFunctions
Generator functions return `IterableIterator<T>`, but a 'normal' function could as well, so the TypeScript return type of `kindOf(*generator...)` is `"generatorfunction" | "function"` as opposed to just `"generatorfunction"` as TypeScript has no way to distinguish the two (to the best of my knowledge).
To test for a function regardless of type you would simply use `kindOf(foo).endsWith("function")`.
'Normal' functions that return any other type will be correctly typed as `"function"` i.e. `kindOf(() => null) == "function"`.

#### Error-like, RegExp-like, Generator-like, Date-like
These type checks will reflect the fact that `kind-of` has fallback checks for `RegExp`, `Error`, `Generator`, and `Date` objects to check for "RegExp-like" and "Error-like" etc...
- The return type of `kindOf({ flags: "", ignoreCase: true, multiline: true, global: true })` will be `"regexp"`.
- The return type of `kindOf({ message: "", constructor: { stackTraceLimit: 0 } })` will be `"error"`.
- The return type of `kindOf({ throw: () => {}, return: () => {}, next: () => {} })` will be `"generator"`.
- The return type of `kindOf({ toDateString: () => {}, getDate: () => {}, setDate: () => {} })` will be `"date"`.

#### Buffers
Buffers in environments where Buffer.isBuffer (or an appropriate polyfill) is unavailable may not correctly be typed as "buffer".
e.g. `kindOf(Buffer.from([]))` could return `"uint8array"` but TypeScript may think the return type is `"buffer"`. */

console.assert(kindOf() === "undefined");
console.assert(kindOf(undefined) === "undefined");
console.assert(kindOf(void 0) === "undefined");

console.assert(kindOf(null) === "null");

console.assert(kindOf(Boolean()) === "boolean");
console.assert(kindOf(new Boolean()) === "boolean");
console.assert(kindOf(true) === "boolean");
console.assert(kindOf(Boolean) === "function");

console.assert(kindOf(Number()) === "number");
console.assert(kindOf(new Number()) === "number");
console.assert(kindOf(0) === "number");
console.assert(kindOf(NaN) === "number");
console.assert(kindOf(Infinity) === "number");
console.assert(kindOf(Number) === "function");

console.assert(kindOf(String()) === "string");
console.assert(kindOf(new String()) === "string");
console.assert(kindOf("") === "string");
console.assert(kindOf((0).toString()) === "string");
console.assert(kindOf(String) === "function");

console.assert(kindOf(Symbol()) === "symbol");

console.assert(kindOf((new Map)[Symbol.iterator]()) === "mapiterator");
console.assert(kindOf((new Set)[Symbol.iterator]()) === "setiterator");
console.assert(kindOf(("")[Symbol.iterator]()) === "stringiterator");
console.assert(kindOf((new Array)[Symbol.iterator]()) === "arrayiterator");

console.assert(kindOf(() => { }) === "function");
console.assert(kindOf(function () { }) === "function");
console.assert(kindOf(function (): IterableIterator<any> { return null as any as IterableIterator<any> }) === "function");
console.assert(kindOf(class { }) === "function");
console.assert(kindOf(function* (): IterableIterator<any> { }) === "generatorfunction");

console.assert(kindOf([]) === "array");
console.assert(kindOf(new Array) === "array");

console.assert(kindOf(new Date) === "date");
// date-like
console.assert(kindOf({ toDateString: () => { }, getDate: () => { }, setDate: () => { } }) === "date");

if (typeof Buffer !== "undefined") console.assert(kindOf(Buffer.from([])) === "buffer");

console.assert(kindOf(new Error) === "error");
// error-like
console.assert(kindOf({ message: "", constructor: { stackTraceLimit: 0 } }) === "error");
console.assert(kindOf({ message: "", constructor: { stackTraceLimit: 0 }, foo: 1 }) === "error");

console.assert(kindOf(new RegExp("a-z")) === "regexp");
console.assert(kindOf(/a-z/) === "regexp");
// regexp-like
console.assert(kindOf({ flags: "", ignoreCase: true, multiline: true, global: true }) === "regexp");
console.assert(kindOf({ flags: "", ignoreCase: true, multiline: true, global: true, foo: 1 }) === "regexp");

console.assert(kindOf(Promise.resolve()) === "promise");

console.assert(kindOf(new WeakMap) === "weakmap");
console.assert(kindOf(new WeakSet) === "weakset");
console.assert(kindOf(new Map) === "map");
console.assert(kindOf(new Set) === "set");

console.assert(kindOf(new Int8Array(8)) === "int8array");
console.assert(kindOf(new Uint8Array(8)) === "uint8array");
console.assert(kindOf(new Uint8ClampedArray(8)) === "uint8clampedarray");
console.assert(kindOf(new Int16Array(8)) === "int16array");
console.assert(kindOf(new Uint16Array(8)) === "uint16array");
console.assert(kindOf(new Int32Array(8)) === "int32array");
console.assert(kindOf(new Uint32Array(8)) === "uint32array");
console.assert(kindOf(new Float32Array(8)) === "float32array");
console.assert(kindOf(new Float64Array(8)) === "float64array");

console.assert((function () { return kindOf(arguments) })() === "arguments");

// generator-like
console.assert(kindOf({ throw: () => { }, return: () => { }, next: () => { } }) === "generator");
console.assert(kindOf({ throw: () => { }, return: () => { }, next: () => { }, foo: 1 }) === "generator");

if (typeof global !== "undefined") console.assert(kindOf(global) === "global");
if (typeof window !== "undefined") console.assert(kindOf(window) === "window");

console.assert(kindOf({}) === "object");
console.assert(kindOf(new (class { })) === "object");
console.assert(kindOf(console) === "object");
30 changes: 30 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"compilerOptions": {
"module": "commonjs",
"esModuleInterop": true,
"target": "es6",
"noImplicitAny": true,
"moduleResolution": "node",
"inlineSourceMap": false,
"sourceMap": true,
"strict": true,
"skipLibCheck": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"declaration": true,
"declarationDir": "./test/typescript",
"outDir": "./test/typescript",
"baseUrl": ".",
"lib": [
"es7",
"dom"
]
},
"files": [
"./index.d.ts",
"./test/typescript/index.ts",
],
"compileOnSave": false,
"buildOnSave": false
}