]> git.ipfire.org Git - thirdparty/vuejs/pinia.git/commitdiff
fix(types): forbid non existant access in getters and actions
authorEduardo San Martin Morote <posva13@gmail.com>
Thu, 24 Jun 2021 10:04:47 +0000 (12:04 +0200)
committerEduardo San Martin Morote <posva13@gmail.com>
Thu, 24 Jun 2021 10:07:59 +0000 (12:07 +0200)
src/store.ts
src/types.ts
test-dts/store.test-d.ts

index 101701693b72c9f23d8bf03856568c7693e1a123..55017eb508999df03f393a520383e5d5c96daeb0 100644 (file)
@@ -413,10 +413,10 @@ export function defineStore<
     if (pinia) setActivePinia(pinia)
     // TODO: worth warning on server if no piniaKey as it can leak data
     pinia = getActivePinia()
-    let stores = storesMap.get(pinia)
-    if (!stores) storesMap.set(pinia, (stores = new Map()))
+    let storeCache = storesMap.get(pinia)
+    if (!storeCache) storesMap.set(pinia, (storeCache = new Map()))
 
-    let storeAndDescriptor = stores.get(id) as
+    let storeAndDescriptor = storeCache.get(id) as
       | [
           StoreWithState<Id, S, G, A>,
           StateDescriptor<S>,
@@ -430,7 +430,7 @@ export function defineStore<
       storeAndDescriptor = initStore(id, state, pinia.state.value[id])
 
       // @ts-expect-error: annoying to type
-      stores.set(id, storeAndDescriptor)
+      storeCache.set(id, storeAndDescriptor)
 
       store = buildStoreToUse<
         Id,
index 7c29becdf4ccc1808122caccc873047341af7095..0141e5713f31105a4ac49665f0da69c8b6e2e0e3 100644 (file)
@@ -226,7 +226,8 @@ export interface StoreWithState<
   /**
    * State of the Store. Setting it will replace the whole state.
    */
-  $state: UnwrapRef<S> & PiniaCustomStateProperties<S>
+  $state: UnwrapRef<StateTree extends S ? {} : S> &
+    PiniaCustomStateProperties<S>
 
   /**
    * Private property defining the pinia the store is attached to.
@@ -387,7 +388,7 @@ export type Store<
  */
 export type GenericStore<
   Id extends string = string,
-  S extends StateTree = StateTree,
+  S extends StateTree = any,
   G extends GettersTree<S> = GettersTree<S>,
   // has the actions without the context (this) for typings
   A /* extends ActionsTree */ = ActionsTree
@@ -442,7 +443,12 @@ export interface PiniaCustomStateProperties<S extends StateTree = StateTree> {}
  */
 export type GettersTree<S extends StateTree> = Record<
   string,
-  ((state: UnwrapRef<S & PiniaCustomStateProperties<S>>) => any) | (() => any)
+  | ((
+      state: UnwrapRef<
+        (StateTree extends S ? {} : S) & PiniaCustomStateProperties<S>
+      >
+    ) => any)
+  | (() => any)
 >
 
 /**
@@ -466,24 +472,30 @@ export interface DefineStoreOptions<
    * Unique string key to identify the store across the application.
    */
   id: Id
+
   /**
    * Function to create a fresh state.
    */
   state?: () => S
+
   /**
    * Optional object of getters.
    */
   getters?: G &
-    ThisType<UnwrapRef<S> & StoreWithGetters<G> & PiniaCustomProperties>
+    ThisType<
+      UnwrapRef<StateTree extends S ? {} : S> &
+        StoreWithGetters<G> &
+        PiniaCustomProperties
+    >
   /**
    * Optional object of actions.
    */
   actions?: A &
     ThisType<
       A &
-        UnwrapRef<S> &
+        UnwrapRef<StateTree extends S ? {} : S> &
         StoreWithState<Id, S, G, A> &
-        StoreWithGetters<G> &
+        StoreWithGetters<GettersTree<S> extends G ? {} : G> &
         PiniaCustomProperties
     >
 }
index 436dfdb6a43e316f63a8c7e5d4a0071138ce6f50..ec81dc16734e23f618d887700db53942a05227bb 100644 (file)
@@ -25,6 +25,8 @@ const useStore = defineStore({
   },
   actions: {
     doStuff() {
+      // @ts-expect-error
+      this.notExisting
       expectType<string>(this.upper)
       expectType<false>(this.other)
     },
@@ -34,6 +36,73 @@ const useStore = defineStore({
   },
 })
 
+// actions on not existing properties
+defineStore({
+  id: '',
+  actions: {
+    a() {
+      // @ts-expect-error
+      this.notExisting
+    },
+  },
+})
+
+defineStore({
+  id: '',
+  state: () => ({}),
+  actions: {
+    a() {
+      // @ts-expect-error
+      this.notExisting
+    },
+  },
+})
+
+defineStore({
+  id: '',
+  getters: {},
+  actions: {
+    a() {
+      // @ts-expect-error
+      this.notExisting
+    },
+  },
+})
+
+// getters on not existing properties
+defineStore({
+  id: '',
+  getters: {
+    a(): number {
+      // @ts-expect-error
+      this.notExisting
+      return 2
+    },
+    b: (state) => {
+      // @ts-expect-error
+      state.notExisting
+      return
+    },
+  },
+})
+
+defineStore({
+  id: '',
+  state: () => ({}),
+  getters: {
+    a(): number {
+      // @ts-expect-error
+      this.notExisting
+      return 2
+    },
+    b: (state) => {
+      // @ts-expect-error
+      state.notExisting
+      return
+    },
+  },
+})
+
 const store = useStore()
 
 expectType<{ a: 'on' | 'off' }>(store.$state)
@@ -169,6 +238,5 @@ expectType<any>(genericStore.thing)
 expectType<any>(genericStore.$state.thing)
 takeStore(genericStore)
 useSyncValueToStore(() => 2, genericStore, 'myState')
-useSyncValueToStore(() => 2, genericStore, 'random')
-// @ts-expect-error
 useSyncValueToStore(() => false, genericStore, 'myState')
+useSyncValueToStore(() => 2, genericStore, 'random')