From 2868065a93b6d38bd5af41f6edee038e72e67123 Mon Sep 17 00:00:00 2001 From: Alec Larson Date: Sat, 30 Mar 2019 16:00:12 -0400 Subject: [PATCH] feat(ts): Support typescript 3.4 BREAKING CHANGE: typescript@3.4.0 is now the minimum required version --- __tests__/immutable.ts | 10 ++--- __tests__/produce.ts | 95 +++++++++++++++++++++++++++--------------- package.json | 2 +- src/immer.d.ts | 32 +++++++++----- yarn.lock | 8 ++-- 5 files changed, 92 insertions(+), 55 deletions(-) diff --git a/__tests__/immutable.ts b/__tests__/immutable.ts index b580a63b..b009a6cf 100644 --- a/__tests__/immutable.ts +++ b/__tests__/immutable.ts @@ -14,19 +14,19 @@ declare const exactType: ( // array in tuple { let val = {} as Immutable<[string[], 1]> - exactType(val, {} as [ReadonlyArray, 1]) + exactType(val, {} as readonly [ReadonlyArray, 1]) } // tuple in array { let val = {} as Immutable<[string, 1][]> - exactType(val, {} as ReadonlyArray<[string, 1]>) + exactType(val, {} as ReadonlyArray) } // tuple in tuple { let val = {} as Immutable<[[string, 1], 1]> - exactType(val, {} as [[string, 1], 1]) + exactType(val, {} as readonly [readonly [string, 1], 1]) } // array in array @@ -38,13 +38,13 @@ declare const exactType: ( // tuple in object { let val = {} as Immutable<{a: [string, 1]}> - exactType(val, {} as {readonly a: [string, 1]}) + exactType(val, {} as {readonly a: readonly [string, 1]}) } // object in tuple { let val = {} as Immutable<[{a: string}, 1]> - exactType(val, {} as [{readonly a: string}, 1]) + exactType(val, {} as readonly [{readonly a: string}, 1]) } // array in object diff --git a/__tests__/produce.ts b/__tests__/produce.ts index 4b31cdb3..90bc5f5c 100644 --- a/__tests__/produce.ts +++ b/__tests__/produce.ts @@ -2,7 +2,8 @@ import produce, { produce as produce2, applyPatches, Patch, - nothing + nothing, + Draft } from "../dist/immer.js" // prettier-ignore @@ -68,23 +69,24 @@ it("can update readonly state via standard api", () => { // NOTE: only when the function type is inferred it("can infer state type from default state", () => { - type Producer = (base: number | undefined) => number - let foo = produce(_ => {}, 1) - exactType(foo, {} as Producer) - exactType(foo(2), 0 as number) + type State = {readonly a: number} | boolean + type Recipe = (base?: State | undefined) => State + + let foo = produce(_ => {}, {} as State) + exactType(foo, {} as Recipe) }) it("can infer state type from recipe function", () => { - type T = string | number - type Producer = (base: T | undefined, _2: number) => T + type State = {readonly a: string} | {readonly b: string} + type Recipe = (base: State | undefined, arg: number) => State - let foo = produce((_: string | number, _2: number) => {}, 1) - exactType(foo, {} as Producer) - exactType(foo("", 0), {} as string | number) + let foo = produce((draft: Draft, arg: number) => {}, {} as any) + exactType(foo, {} as Recipe) }) it("cannot infer state type when the function type and default state are missing", () => { - exactType(produce(_ => {}), {} as (base: any) => any) + const res = produce(_ => {}) + exactType(res, {} as (base: any) => any) }) it("can update readonly state via curried api", () => { @@ -136,25 +138,53 @@ it("can apply patches", () => { describe("curried producer", () => { it("supports rest parameters", () => { - type Foo = (base: {}, _2: number, _3: number) => {} - let foo = produce((_1: {}, _2: number, _3: number) => {}) - exactType(foo, {} as Foo) - foo({}, 1, 2) + type State = {readonly a: 1} + + // No initial state: + let foo = produce(() => {}) + exactType(foo, {} as (base: State, ...args: number[]) => State) + foo({} as State, 1, 2) + + // TODO: Using argument parameters + // let woo = produce((state: Draft, ...args: number[]) => {}) + // exactType(woo, {} as (base: State, ...args: number[]) => State) + // woo({} as State, 1, 2) // With initial state: - type Bar = (base: {} | undefined, _2: number, _3: number) => {} - let bar = produce((_1: {}, _2: number, _3: number) => {}, {}) - exactType(bar, {} as Bar) - bar(undefined, 1, 2) + let bar = produce( + (state: Draft, ...args: number[]) => {}, + {} as State + ) + exactType(bar, {} as (base?: State, ...args: number[]) => State) + bar({} as State | undefined, 1, 2) + bar() + + // When args is a tuple: + let tup = produce( + (state: Draft, ...args: [string, ...number[]]) => {}, + {} as State + ) + exactType(tup, {} as ( + base: State | undefined, + arg1: string, + ...args: number[] + ) => State) + tup({a: 1}, "", 2) + tup(undefined, "", 2) }) it("can be passed a readonly array", () => { - let foo = produce((_: any[]) => {}) + // No initial state: + let foo = produce>(() => {}) + exactType(foo, {} as (base: readonly any[]) => readonly any[]) foo([] as ReadonlyArray) // With initial state: - let bar = produce((_: any[]) => {}, []) + let bar = produce(() => {}, [] as ReadonlyArray) + exactType(bar, {} as (base?: readonly any[]) => readonly any[]) bar([] as ReadonlyArray) + bar(undefined) + bar() }) }) @@ -219,23 +249,20 @@ it("can return the draft itself", () => { }) it("can return a promise", () => { - let base = {} as {readonly a: number} + type Base = {readonly a: number} + let base = {} as Base // Return a promise only. - exactType( - produce(base, draft => { - return Promise.resolve(draft.a > 0 ? null : undefined) - }), - {} as Promise<{readonly a: number} | null> - ) + let res1 = produce(base, draft => { + return Promise.resolve(draft.a > 0 ? null : undefined) + }) + exactType(res1, {} as Promise) // Return a promise or undefined. - exactType( - produce(base, draft => { - if (draft.a > 0) return Promise.resolve() - }), - {} as (Promise<{readonly a: number}> | {readonly a: number}) - ) + let res2 = produce(base, draft => { + if (draft.a > 0) return Promise.resolve() + }) + exactType(res2, {} as Base | Promise) }) it("works with `void` hack", () => { diff --git a/package.json b/package.json index 6b418526..71583775 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "regenerator-runtime": "^0.11.1", "rimraf": "^2.6.2", "seamless-immutable": "^7.1.3", - "typescript": "3.1.1", + "typescript": "3.4.3", "yarn-or-npm": "^2.0.4" }, "jest": { diff --git a/src/immer.d.ts b/src/immer.d.ts index aa370e94..b6b62cab 100644 --- a/src/immer.d.ts +++ b/src/immer.d.ts @@ -92,22 +92,32 @@ export interface IProduce { * @param {Function} patchListener - optional function that will be called with all the patches produced here * @returns {any} a new state, or the initial state if nothing was modified */ - >( - base: T, - recipe: (this: D, draft: D) => Return, + ( + base: Base extends Function ? never : Base, + recipe: (this: Draft, draft: Draft) => Return, listener?: PatchListener - ): Produced + ): Produced /** Curried producer with a default value */ - >( - recipe: (this: D, draft: D, ...rest: Rest) => Return, - defaultBase: T - ): (base: Immutable | undefined, ...rest: Rest) => Produced + ( + recipe: (this: Base, draft: Base, ...rest: Rest) => Return, + defaultBase: Immutable + ): Rest[number][] extends Rest | never[] + ? ( + // The `base` argument is optional when `Rest` is optional. + base?: Immutable, + ...rest: Rest + ) => Produced, Return> + : ( + // The `base` argument is required when `Rest` is required. + base: Immutable | undefined, + ...rest: Rest + ) => Produced, Return> /** Curried producer with no default value */ - ( - recipe: (this: Draft, draft: Draft, ...rest: Rest) => Return - ): (base: Immutable, ...rest: Rest) => Produced + ( + recipe: (this: Draft, draft: Draft, ...rest: Rest) => Return + ): (base: Immutable, ...rest: Rest) => Produced } export const produce: IProduce diff --git a/yarn.lock b/yarn.lock index 02e59d3d..c190dd0a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6049,10 +6049,10 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" -typescript@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.1.1.tgz#3362ba9dd1e482ebb2355b02dfe8bcd19a2c7c96" - integrity sha512-Veu0w4dTc/9wlWNf2jeRInNodKlcdLgemvPsrNpfu5Pq39sgfFjvIIgTsvUHCoLBnMhPoUA+tFxsXjU6VexVRQ== +typescript@3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.3.tgz#0eb320e4ace9b10eadf5bc6103286b0f8b7c224f" + integrity sha512-FFgHdPt4T/duxx6Ndf7hwgMZZjZpB+U0nMNGVCYPq0rEzWKjEDobm4J6yb3CS7naZ0yURFqdw9Gwc7UOh/P9oQ== uglify-js@^3.1.4: version "3.5.3"