Skip to content

Commit

Permalink
Add .nullable property (#89)
Browse files Browse the repository at this point in the history
  • Loading branch information
jhoermann authored Aug 4, 2022
1 parent 430d690 commit 23899f3
Show file tree
Hide file tree
Showing 33 changed files with 562 additions and 6 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ module.exports = {

// eslint-plugin-unicorn
'unicorn/filename-case': 'off',
'unicorn/no-null': 'off',
'unicorn/no-useless-undefined': 'off', // conflicts with consistent-return
'unicorn/prevent-abbreviations': ['warn', {
replacements: {
Expand Down Expand Up @@ -155,7 +156,6 @@ module.exports = {
'jest/require-top-level-describe': 'warn',

// less strict other rules
'unicorn/no-null': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-unsafe-argument': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
Expand Down
51 changes: 46 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,18 @@ Vue.component('MyComponent', {
props: {
disabled: booleanProp().withDefault(false),
title: stringProp().optional,
description: stringProp().nullable,
items: arrayProp<string>().required,
callback: functionProp<() => void>().optional,
color: oneOfProp(['red', 'green', 'blue'] as const).withDefault('red'),
},
mounted() {
this.disabled // type: boolean
this.title // type: string | undefined
this.items // type: string[]
this.callback // type: (() => void) | undefined
this.color // type: 'red' | 'green' | 'blue'
this.disabled // type: boolean
this.title // type: string | undefined
this.description // type: string | null
this.items // type: string[]
this.callback // type: (() => void) | undefined
this.color // type: 'red' | 'green' | 'blue'
},
});
```
Expand All @@ -41,6 +43,7 @@ npm install vue-ts-types
Each of the prop functions returns an object with the following properties:

* `.optional`: Use this to mark the prop as not required with a default value of `undefined`. Also includes `undefined` in the resulting prop type.
* `.nullable`: Use this to mark the prop as not required with a default value of `null`. Also includes `null` in the resulting prop type.
* `.required`: Use this to mark the prop as required without a default value.
* `.withDefault(value)`: Use this to set a default value for the prop. Note that the value has to fit the prop type. For non-primitive types, the value has to be a function that returns the default value.

Expand Down Expand Up @@ -72,6 +75,8 @@ Type parameter `T` can be used to restrict the type at compile time with a union
```ts
stringProp().optional
// → prop type: string | undefined
stringProp().nullable
// → prop type: string | null
stringProp().required
// → prop type: string
stringProp().withDefault('foo')
Expand All @@ -81,6 +86,8 @@ type Foo = 'a' | 'b' | 'c';

stringProp<Foo>().optional
// → prop type: Foo | undefined
stringProp<Foo>().nullable
// → prop type: Foo | null
stringProp<Foo>().required
// → prop type: Foo
stringProp<Foo>().withDefault('a')
Expand All @@ -94,6 +101,8 @@ Allows any boolean (validated at runtime and compile time).
```ts
booleanProp().optional
// → prop type: boolean | undefined
booleanProp().nullable
// → prop type: boolean | null
booleanProp().required
// → prop type: boolean
booleanProp().withDefault(false)
Expand All @@ -107,6 +116,8 @@ Allows any number (validated at runtime and compile time).
```ts
numberProp().optional
// → prop type: number | undefined
numberProp().nullable
// → prop type: number | null
numberProp().required
// → prop type: number
numberProp().withDefault(3.1415)
Expand All @@ -120,6 +131,8 @@ Allows any integer (validated at runtime).
```ts
integerProp().optional
// → prop type: number | undefined
integerProp().nullable
// → prop type: number | null
integerProp().required
// → prop type: number
integerProp().withDefault(42)
Expand All @@ -133,6 +146,8 @@ Allows any symbol (validated at runtime and compile time).
```ts
symbolProp().optional
// → prop type: symbol | undefined
symbolProp().nullable
// → prop type: symbol | null
symbolProp().required
// → prop type: symbol
symbolProp().withDefault(Symbol('foo'))
Expand All @@ -146,6 +161,8 @@ Allows any Vue component instance, name or options object. No built-in runtime v
```ts
vueComponentProp().optional
// → prop type: ComponentOptions<Vue> | VueConstructor<Vue> | string | undefined
vueComponentProp().nullable
// → prop type: ComponentOptions<Vue> | VueConstructor<Vue> | string | null
vueComponentProp().required
// → prop type: ComponentOptions<Vue> | VueConstructor<Vue> | string
vueComponentProp().withDefault('close-icon')
Expand All @@ -160,13 +177,17 @@ Type parameter `T` can be used to restrict the type at compile time.
```ts
anyProp().optional
// → prop type: any
anyProp().nullable
// → prop type: any
anyProp().required
// → prop type: any
anyProp().withDefault('foo')
// → prop type: any

anyProp<string>().optional
// → prop type: string | undefined
anyProp<string>().nullable
// → prop type: string | null
anyProp<string>().required
// → prop type: string
anyProp<string>().withDefault('foo')
Expand All @@ -181,13 +202,17 @@ Type parameter `T` can be used to restrict the type of the array items at compil
```ts
arrayProp().optional
// → prop type: unknown[] | undefined
arrayProp().nullable
// → prop type: unknown[] | null
arrayProp().required
// → prop type: unknown[]
arrayProp().withDefault(() => [])
// → prop type: unknown[]

arrayProp<string>().optional
// → prop type: string[] | undefined
arrayProp<string>().nullable
// → prop type: string[] | null
arrayProp<string>().required
// → prop type: string[]
arrayProp<string>().withDefault(() => ['foo', 'bar'])
Expand All @@ -202,6 +227,8 @@ Type parameter `T` can be used to restrict the type at compile time.
```ts
objectProp().optional
// → prop type: object | undefined
objectProp().nullable
// → prop type: object | null
objectProp().required
// → prop type: object
objectProp().withDefault(() => ({}))
Expand All @@ -213,6 +240,8 @@ interface User {

objectProp<User>().optional
// → prop type: User | undefined
objectProp<User>().nullable
// → prop type: User | null
objectProp<User>().required
// → prop type: User
objectProp<User>().withDefault(() => ({ name: 'John' }))
Expand All @@ -229,13 +258,17 @@ Type parameter `T` can be used to restrict the type to a specific function signa
```ts
functionProp().optional
// → prop type: Function | undefined
functionProp().nullable
// → prop type: Function | null
functionProp().required
// → prop type: Function

type MyFunc = (a: number, b: string) => boolean;

functionProp<MyFunc>().optional
// → prop type: MyFunc | undefined
functionProp<MyFunc>().nullable
// → prop type: MyFunc | null
functionProp<MyFunc>().required
// → prop type: MyFunc
```
Expand All @@ -250,6 +283,8 @@ Type parameter `T` can be used to adjust the inferred type at compile time, this
```ts
oneOfProp(['foo', 'bar'] as const).optional
// → prop type: 'foo' | 'bar' | undefined
oneOfProp(['foo', 'bar'] as const).nullable
// → prop type: 'foo' | 'bar' | null
oneOfProp(['foo', 'bar'] as const).required
// → prop type: 'foo' | 'bar'
oneOfProp(['foo', 'bar'] as const).withDefault('foo')
Expand All @@ -264,6 +299,8 @@ Type parameter `T` can be used to adjust the inferred type at compile time, this
```ts
oneOfObjectKeysProp({ foo: 1, bar: 2 }).optional
// → prop type: 'foo' | 'bar' | undefined
oneOfObjectKeysProp({ foo: 1, bar: 2 }).nullable
// → prop type: 'foo' | 'bar' | null
oneOfObjectKeysProp({ foo: 1, bar: 2 }).required
// → prop type: 'foo' | 'bar'
oneOfObjectKeysProp({ foo: 1, bar: 2 }).withDefault('foo')
Expand All @@ -278,6 +315,8 @@ Type parameter `T` has to be used to adjust the type at compile time.
```ts
oneOfTypesProp<number | string>([Number, String]).optional
// → prop type: string | number | undefined
oneOfTypesProp<number | string>([Number, String]).nullable
// → prop type: string | number | null
oneOfTypesProp<number | string>([Number, String]).required
// → prop type: string | number
oneOfTypesProp<number | string>([Number, String]).withDefault(42)
Expand All @@ -292,6 +331,8 @@ Type parameter `T` can be used to adjust the inferred type at compile time.
```ts
instanceOfProp(Date).optional
// → prop type: Date | undefined
instanceOfProp(Date).nullable
// → prop type: Date | null
instanceOfProp(Date).required
// → prop type: Date
instanceOfProp(Date).withDefault(() => new Date())
Expand Down
7 changes: 7 additions & 0 deletions src/prop-types/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { vuePropValidator } from '../validators';

interface FunctionPropOptionsGenerator<T> {
optional: DefaultPropOptions<T | undefined> & { default?: () => T };
nullable: DefaultPropOptions<T | null> & { default?: (() => T) | null };
required: RequiredPropOptions<T> & { default?: () => T };
}

Expand All @@ -23,6 +24,12 @@ export const functionProp = <T extends Function>(validator?: Validator): Functio
default: undefined,
validator: vuePropValidator(validator),
},
nullable: {
type: Function as unknown as () => T,
required: false,
default: null,
validator: vuePropValidator(validator),
},
required: {
type: Function as unknown as () => T,
required: true,
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export type DefaultPropOptions<T> = PropOptions<T> & { default: unknown }

export interface PropOptionsGenerator<T> {
optional: DefaultPropOptions<T | undefined>;
nullable: DefaultPropOptions<T | null>;
withDefault: (defaultValue: OneOfDefaultType<T>) => DefaultPropOptions<T>;
required: RequiredPropOptions<T>;
}
Expand Down
6 changes: 6 additions & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ export const propOptionsGenerator = <T>(type?: PropType<T>, userValidator?: Vali
default: undefined,
validator: vuePropValidator(userValidator, ...typeValidators),
},
nullable: {
type,
required: false,
default: null,
validator: vuePropValidator(userValidator, ...typeValidators),
},
withDefault: (defaultValue: OneOfDefaultType<T>): DefaultPropOptions<T> => ({
type,
required: false,
Expand Down
11 changes: 11 additions & 0 deletions tests/prop-types/any.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,17 @@ describe('anyProp().optional', () => {
});
});

describe('anyProp().nullable', () => {
it('creates the correct prop options', () => {
expect(anyProp().nullable).toStrictEqual({
type: undefined,
required: false,
default: null,
validator: undefined,
});
});
});

describe('anyProp().withDefault', () => {
it('creates the correct prop options', () => {
expect(anyProp().withDefault('foo')).toStrictEqual({
Expand Down
11 changes: 11 additions & 0 deletions tests/prop-types/array.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,17 @@ describe('arrayProp().optional', () => {
});
});

describe('arrayProp().nullable', () => {
it('creates the correct prop options', () => {
expect(arrayProp().nullable).toStrictEqual({
type: Array,
required: false,
default: null,
validator: undefined,
});
});
});

const defaultGenerator = () => ['foo'];

describe('arrayProp().withDefault', () => {
Expand Down
11 changes: 11 additions & 0 deletions tests/prop-types/boolean.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,17 @@ describe('booleanProp().optional', () => {
});
});

describe('booleanProp().nullable', () => {
it('creates the correct prop options', () => {
expect(booleanProp().nullable).toStrictEqual({
type: Boolean,
required: false,
default: null,
validator: undefined,
});
});
});

describe('booleanProp().withDefault', () => {
it('creates the correct prop options', () => {
expect(booleanProp().withDefault(false)).toStrictEqual({
Expand Down
11 changes: 11 additions & 0 deletions tests/prop-types/function.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,17 @@ describe('functionProp().optional', () => {
});
});

describe('functionProp().nullable', () => {
it('creates the correct prop options', () => {
expect(functionProp().nullable).toStrictEqual({
type: Function,
required: false,
default: null,
validator: undefined,
});
});
});

describe('functionProp().required', () => {
it('creates the correct prop options', () => {
expect(functionProp().required).toStrictEqual({
Expand Down
11 changes: 11 additions & 0 deletions tests/prop-types/instanceOf.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@ describe('instanceOfProp().optional', () => {
});
});

describe('instanceOfProp().nullable', () => {
it('creates the correct prop options', () => {
expect(instanceOfProp(User).nullable).toStrictEqual({
type: User,
required: false,
default: null,
validator: expect.any(Function),
});
});
});

describe('instanceOfProp().withDefault', () => {
it('creates the correct prop options', () => {
expect(instanceOfProp(User).withDefault(() => new User())).toStrictEqual({
Expand Down
11 changes: 11 additions & 0 deletions tests/prop-types/integer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,17 @@ describe('integerProp().optional', () => {
});
});

describe('integerProp().nullable', () => {
it('creates the correct prop options', () => {
expect(integerProp().nullable).toStrictEqual({
type: Number,
required: false,
default: null,
validator: expect.any(Function),
});
});
});

describe('integerProp().withDefault', () => {
it('creates the correct prop options', () => {
expect(integerProp().withDefault(27)).toStrictEqual({
Expand Down
11 changes: 11 additions & 0 deletions tests/prop-types/number.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,17 @@ describe('numberProp().optional', () => {
});
});

describe('numberProp().nullable', () => {
it('creates the correct prop options', () => {
expect(numberProp().nullable).toStrictEqual({
type: Number,
required: false,
default: null,
validator: undefined,
});
});
});

describe('numberProp().withDefault', () => {
it('creates the correct prop options', () => {
expect(numberProp().withDefault(27)).toStrictEqual({
Expand Down
Loading

0 comments on commit 23899f3

Please sign in to comment.