Remove duplication from exhaustiveness checks for discriminating unions

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
Janne SavolainenandMikko Aspiala committed 2022-10-21 08:23:25 +03:00
1 parent 497ab273ae
commit 1a5073caad
4 files changed
+27 -44

No files matched your search

@@ -5,3 +5,13 @@
// See: https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#discriminating-unions
export interface Discriminable<T extends string> { kind: T }
// Note: this will fail at transpilation time, if all kinds are not instructed in switch/case.
// See: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#exhaustiveness-checking
export const checkThatAllDiscriminablesAreExhausted = <T extends never>(value: T) => {
const _exhaustiveCheck: never = value;
return new Error(
`Tried to exhaust discriminables, but no instructions were found for ${(_exhaustiveCheck as any).kind}`,
);
};
@@ -10,6 +10,7 @@ import type { ApplicationMenuItemTypes } from "./menu-items/application-menu-ite
import { pipeline } from "@ogre-tools/fp";
import { map, sortBy } from "lodash/fp";
import type { MenuItemRoot } from "./application-menu-item-composite.injectable";
import { checkThatAllDiscriminablesAreExhausted } from "../../../common/utils/composable-responsibilities/discriminable/discriminable";
const populateApplicationMenuInjectable = getInjectable({
id: "populate-application-menu",
@@ -32,12 +33,12 @@ export default populateApplicationMenuInjectable;
const toHierarchicalElectronMenuItem = (
composite: Composite<ApplicationMenuItemTypes>,
): MenuItemOpts => {
switch (composite.value.kind) {
const value = composite.value;
switch (value.kind) {
case "top-level-menu": {
const {
id,
value: { label, role },
} = composite;
const { id } = composite;
const { label, role } = value;
return {
...(id ? { id } : {}),
@@ -53,10 +54,8 @@ const toHierarchicalElectronMenuItem = (
}
case "sub-menu": {
const {
id,
value: { label },
} = composite;
const { id } = composite;
const { label } = value;
return {
...(id ? { id } : {}),
@@ -71,10 +70,8 @@ const toHierarchicalElectronMenuItem = (
}
case "clickable-menu-item": {
const {
id,
value: { label, onClick, keyboardShortcut },
} = composite;
const { id } = composite;
const { label, onClick, keyboardShortcut } = value;
return {
...(id ? { id } : {}),
@@ -85,9 +82,7 @@ const toHierarchicalElectronMenuItem = (
}
case "os-action-menu-item": {
const {
value: { label, keyboardShortcut, actionName },
} = composite;
const { label, keyboardShortcut, actionName } = value;
return {
...(label ? { label } : {}),
@@ -103,14 +98,7 @@ const toHierarchicalElectronMenuItem = (
}
default: {
// Note: this will fail at transpilation time, if all ApplicationMenuItemTypes
// are not handled in switch/case.
const _exhaustiveCheck: never = composite.value;
// Note: this code is unreachable, it is here to make ts not complain about
// _exhaustiveCheck not being used.
// See: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#exhaustiveness-checking
throw new Error(`Tried to create application menu, but foreign menu item was encountered: ${_exhaustiveCheck} ${composite.value}`);
throw checkThatAllDiscriminablesAreExhausted(value);
}
}
};
@@ -15,6 +15,7 @@ import { PreferencesNavigationTab } from "./preferences-navigation-tab";
import { compositeHasDescendant } from "../../../../common/utils/composite/composite-has-descendant/composite-has-descendant";
import type { PreferenceTabsRoot } from "../preference-items/preference-tab-root";
import { Icon } from "../../../../renderer/components/icon";
import { checkThatAllDiscriminablesAreExhausted } from "../../../../common/utils/composable-responsibilities/discriminable/discriminable";
interface Dependencies {
composite: IComputedValue<Composite<PreferenceTypes | PreferenceTabsRoot>>;
@@ -88,16 +89,7 @@ const toNavigationHierarchy = (composite: Composite<PreferenceTypes | Preference
}
default: {
// Note: this will fail at transpilation time, if all kinds
// are not handled in switch/case.
const _exhaustiveCheck: never = value;
// Note: this code is unreachable, it is here to make ts not complain about
// _exhaustiveCheck not being used.
// See: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#exhaustiveness-checking
throw new Error(
`Tried to create preference navigation, but foreign item was encountered: ${_exhaustiveCheck} ${composite.value}`,
);
throw checkThatAllDiscriminablesAreExhausted(value);
}
}
};
@@ -108,3 +100,4 @@ const hasContent = compositeHasDescendant<PreferenceTypes | PreferenceTabsRoot>(
const emptyRender = <></>;
@@ -16,6 +16,7 @@ import { Map } from "../../../renderer/components/map/map";
import { observer } from "mobx-react";
import { PreferencesNavigation } from "./preference-navigation/preferences-navigation";
import Gutter from "../../../renderer/components/gutter/gutter";
import { checkThatAllDiscriminablesAreExhausted } from "../../../common/utils/composable-responsibilities/discriminable/discriminable";
interface Dependencies {
closePreferences: () => void;
@@ -94,16 +95,7 @@ const toPreferenceItemHierarchy = (composite: Composite<PreferenceTypes>) => {
}
default: {
// Note: this will fail at transpilation time, if all kinds
// are not handled in switch/case.
const _exhaustiveCheck: never = value;
// Note: this code is unreachable, it is here to make ts not complain about
// _exhaustiveCheck not being used.
// See: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#exhaustiveness-checking
throw new Error(
`Tried to create preferences, but foreign item was encountered: ${_exhaustiveCheck} ${value}`,
);
throw checkThatAllDiscriminablesAreExhausted(value);
}
}
};