Skip to content

Latest commit

 

History

History
435 lines (424 loc) · 14.5 KB

SUPPORTED_TYPES.md

File metadata and controls

435 lines (424 loc) · 14.5 KB

ts-type-checked

< Back to project

Supported types

ts-type-checked supports (reasonably large but still only) a subset of TypeScript features:

Type / feature Support Notes Implementation
bigint,
boolean,
number,
string,
symbol
Primitive types that can be checked using the typeof operator typeof value === 'bigint',
typeof value === 'boolean',
typeof value === 'number',
typeof value === 'string',
typeof value === 'symbol'
BigInt,
Boolean,
Number,
String,
Symbol
Boxed types are converted to their un-boxed versions and checked accordingly typeof value === 'bigint',
typeof value === 'boolean',
typeof value === 'number',
typeof value === 'string',
typeof value === 'symbol'
object All non-primitives (See description of the object type here) typeof value === 'function' || (typeof value === 'object' && value !== null)
Object All objects that inherit from Object (i.e. everything except for null and undefined) and implement the Object interface.

Check the definition of the Object interface here here
value !== null && value !== undefined && typeof value.toString === 'function' && ...
Date Date objects value instanceof Date
Set<T> ES6 Sets

Array.from and Array.every methods are used so they need to be available.
(value instanceof Set) && Array.from(value.values()).every(value => isA<T>(value))
Map<K, V> ES6 Maps

Array.from and Array.every methods are used so they need to be available.
(value instanceof Map) && Array.from(value.entries()).every(entry => isA<K>(entry[0]) && isA<V>(entry[1]))
interface T {
  name: string;
  age: number;
  others: T[];
  // ...
}
,
type T = {
  name: string;
  age: number;
  others: T[];
  // ...
}
All objects that inherit from Object (i.e. everything except for null and undefined) and have all the properties of interface T.

Recursive types are also supported.
value !== null && value !== undefined && typeof value.name === 'string' && typeof value.age === 'number' && isA<T[]>(value.others)
class A Classes Classes are checked according to the duck test: If it walks like a duck and it quacks like a duck, then it must be a duck.

That also means that private and protected members are not checked. See interfaces
Record<string, T>,
Record<number, T>,
{
  [key: string]: T;
}
{
  [key: number]: T;
}
{
  [key: number]: T;
  [key: string]: U;
}
~ All non-primitives (See description of the object type here) whose properties are assignable to T

Object.keys and Array.every methods are used to check the properties so they need to be available

The support for numeric indexes works something like this: if a key can be casted to a number (not a NaN) OR is equal to "NaN" then the value is checked against the index type.
(typeof value === 'function' || (typeof value === 'object' && value !== null)) && Object.keys(value).every(key => isA<T>(value[key]))

Or for numeric indexes:

(typeof value === 'function' || (typeof value === 'object' && value !== null)) && Object.keys(value).every(key => (isNaN(parseFloat(key)) && key !== 'NaN') || isA<T>(value[key]))
'primary',
21,
true,
false,
null,
undefined
Literal types value === 'primary',
value === 21,
value === true,
value === false,
value === null,
value === undefined,
A | B Union types isA<A>(value) || isA<B>(value)
A & B Intersection types isA<A>(value) && isA<B>(value)
Type<T> Generic types are supported provided all the type arguments (T in this case) are specified.

What this means is that you cannot create a generic function and use the isA to check whether a value is assignable to a type parameter of that function.

Conditional types are also supported as part of generic types.
T[],
Array<T>,
ReadonlyArray<T>,
Array types are checked using Array.isArray, the types of elements using Array.every so these need to be available Array.isArray(value) && value.every(element => isA<T>(element))
[T, U, V] Tuple types are checked for their length as well as the types of their members Array.isArray(value) && value.length === 3 && isA<T>(value[0]) && isA<U>(value[1]) && isA<V>(value[2])
Function,
(...args: any[]) => any,
new () => {}
~ The signature of the function cannot be checked (since you cannot check the return type of a function without calling it) typeof value === 'function'
interface T {
  () => string;
  callCount: number;
}
~ The signature of the function cannot be checked (since you cannot check the return type of a function without calling it) typeof value === 'function' && typeof(value.callCount) === 'number'
Promise<T> ~ The resolution value of the promise cannot be checked.

Checking for promise types is discouraged in favor of using the Promise.resolve method.
!!value && typeof value.then === 'function' && typeof value.catch === 'function'
RegExp RegExp objects value instanceof RegExp
Node,
Element,
HTMLElement,
HTMLDivElement
The global DOM classes value instanceof Node,
value instanceof Element,
value instanceof HTMLElement,
value instanceof HTMLDivElement
any,
unknown
Checks for these will always be true true
never Checks for never will always be false false

What is not supported

  • Promise resolution values It is impossible to check what the value of a resolved promise will be
  • Function return types and signatures It is impossible to check anything about a function apart from the fact that it is a function

Pitfalls

When strictNullChecks is disabled in you tsconfig.json the value of ts-type-checked decreases massively - if e.g. any/all properties of an object can be null or undefined, there is no difference between let's say a string 'hello world' and an object of type MyInterface:

interface MyInterface {
  myProperty: string;
}

Why? Because string.myProperty is undefined! The moral of the story: turn strictNullChecks on, it's good for your code!