Add lint rule to protect against this access on unbound methods (#32578)

* Add Actions to ViewModel utility types and specify `this: void` signature

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Add https://typescript-eslint.io/rules/unbound-method/ linter to shared-components

also fix stray lint config which doesn't apply to SC

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Add https://typescript-eslint.io/rules/unbound-method/ linter to element-web

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix genuine issues identified by the linter

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Specify this:void on i18napi

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update Module API

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Add comment for MapToVoidThis

Added utility type to map VM actions to unbound functions.

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski
2026-02-23 15:37:58 +00:00
committed by GitHub
parent 87b28b725c
commit 77670eb369
135 changed files with 285 additions and 263 deletions
@@ -9,15 +9,22 @@ Please see LICENSE files in the repository root for full details.
* The interface for a generic View Model passed to the shared components.
* The snapshot is of type T which is a type specifying a snapshot for the view in question.
*/
export interface ViewModel<T> {
// Utility type to map all VM actions to unbound functions so that they do not have
// to be called with the correct 'this' context. This prevents "cannot read X of undefined" bugs.
type MapToVoidThis<T> = {
[K in keyof T]: T[K] extends (...args: infer A) => infer R ? (this: void, ...args: A) => R : T[K];
};
export type ViewModel<Snapshot, Actions = unknown> = {
/**
* The current snapshot of the view model.
*/
getSnapshot: () => T;
getSnapshot: () => Snapshot;
/**
* Subscribes to changes in the view model.
* The listener will be called whenever the snapshot changes.
*/
subscribe: (listener: () => void) => () => void;
}
} & MapToVoidThis<Actions>;