Skip to content

Commit 5aeaf3f

Browse files
committed
Strengthen types
1 parent 43fbdd7 commit 5aeaf3f

File tree

5 files changed

+93
-40
lines changed

5 files changed

+93
-40
lines changed

packages/cursorless-engine/src/DefaultSpokenFormMap.ts

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { mapValues } from "lodash";
22
import {
3-
PartialSpokenFormTypes,
43
SpokenFormMap,
54
SpokenFormMapEntry,
65
SpokenFormMapKeyTypes,
6+
SpokenFormMappingType,
7+
mapSpokenForms,
78
} from "./SpokenFormMap";
89

910
type DefaultSpokenFormMapDefinition = {
@@ -194,35 +195,26 @@ export interface DefaultSpokenFormMapEntry {
194195
isPrivate: boolean;
195196
}
196197

197-
export type DefaultSpokenFormMap = {
198-
readonly [K in keyof SpokenFormMapKeyTypes]: K extends PartialSpokenFormTypes
199-
? Readonly<
200-
Partial<Record<SpokenFormMapKeyTypes[K], DefaultSpokenFormMapEntry>>
201-
>
202-
: Record<SpokenFormMapKeyTypes[K], DefaultSpokenFormMapEntry>;
203-
};
198+
export type DefaultSpokenFormMap =
199+
SpokenFormMappingType<DefaultSpokenFormMapEntry>;
204200

205201
/**
206202
* This map contains information about the default spoken forms for all our
207203
* speakable entities, including scope types, paired delimiters, etc. Note that
208204
* this map can't be used as a spoken form map. If you want something that can
209205
* be used as a spoken form map, see {@link defaultSpokenFormMap}.
210206
*/
211-
export const defaultSpokenFormInfo = mapValues(
207+
export const defaultSpokenFormInfo: DefaultSpokenFormMap = mapSpokenForms(
212208
defaultSpokenFormMapCore,
213-
(entry) =>
214-
mapValues(entry, (subEntry) =>
215-
typeof subEntry === "string"
216-
? {
217-
defaultSpokenForms: [subEntry],
218-
isDisabledByDefault: false,
219-
isPrivate: false,
220-
}
221-
: subEntry,
222-
),
223-
// FIXME: Don't cast here; need to make our own mapValues with stronger typing
224-
// using tricks from our object.d.ts
225-
) as DefaultSpokenFormMap;
209+
(subEntry) =>
210+
typeof subEntry === "string"
211+
? {
212+
defaultSpokenForms: [subEntry],
213+
isDisabledByDefault: false,
214+
isPrivate: false,
215+
}
216+
: subEntry,
217+
);
226218

227219
/**
228220
* A spoken form map constructed from the default spoken forms. It is designed to

packages/cursorless-engine/src/SpokenFormMap.ts

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,17 @@ export interface SpokenFormMapEntry {
9898
isPrivate: boolean;
9999
}
100100

101+
/**
102+
* A type that contains all the keys of {@link SpokenFormMapKeyTypes}, each of
103+
* whose values are a map from the allowed identifiers for that key to a particular
104+
* value type {@link T}.
105+
*/
106+
export type SpokenFormMappingType<T> = {
107+
readonly [K in SpokenFormType]: K extends PartialSpokenFormTypes
108+
? Readonly<Partial<Record<SpokenFormMapKeyTypes[K], T>>>
109+
: Readonly<Record<SpokenFormMapKeyTypes[K], T>>;
110+
};
111+
101112
/**
102113
* A spoken form map contains information about the spoken forms for all our
103114
* speakable entities, including scope types, paired delimiters, etc. It can
@@ -108,8 +119,35 @@ export interface SpokenFormMapEntry {
108119
* Each key of this map is a type of spoken form, eg `simpleScopeTypeType`, and
109120
* the value is a map of identifiers to {@link SpokenFormMapEntry}s.
110121
*/
111-
export type SpokenFormMap = {
112-
readonly [K in keyof SpokenFormMapKeyTypes]: K extends PartialSpokenFormTypes
113-
? Readonly<Partial<Record<SpokenFormMapKeyTypes[K], SpokenFormMapEntry>>>
114-
: Readonly<Record<SpokenFormMapKeyTypes[K], SpokenFormMapEntry>>;
115-
};
122+
export type SpokenFormMap = SpokenFormMappingType<SpokenFormMapEntry>;
123+
124+
/**
125+
* Converts a spoken form map to a spoken form component map for use in spoken
126+
* form generation.
127+
* @param spokenFormMap The spoken form map to convert to a spoken form
128+
* component map
129+
* @returns A spoken form component map that can be used to generate spoken
130+
* forms
131+
*/
132+
export function mapSpokenForms<I, O>(
133+
input: SpokenFormMappingType<I>,
134+
mapper: <T extends SpokenFormType>(
135+
input: I,
136+
spokenFormType: T,
137+
id: SpokenFormMapKeyTypes[T],
138+
) => O,
139+
): SpokenFormMappingType<O> {
140+
return Object.fromEntries(
141+
Object.entries(input).map(([spokenFormType, map]) => [
142+
spokenFormType,
143+
Object.fromEntries(
144+
Object.entries(map).map(([id, inputValue]) => [
145+
id,
146+
mapper(inputValue!, spokenFormType as SpokenFormType, id),
147+
]),
148+
),
149+
]),
150+
// FIXME: Don't cast here; need to make our own mapValues with stronger typing
151+
// using tricks from our object.d.ts
152+
) as SpokenFormMappingType<O>;
153+
}

packages/cursorless-engine/src/generateSpokenForm/SpokenFormComponent.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { SpokenFormMapEntry, SpokenFormType } from "../SpokenFormMap";
1+
import {
2+
SpokenFormMapEntry,
3+
SpokenFormMapKeyTypes,
4+
SpokenFormType,
5+
} from "../SpokenFormMap";
26

37
/**
48
* A component of a spoken form used internally during spoken form generation.
@@ -17,14 +21,18 @@ export type SpokenFormComponent =
1721
| string
1822
| SpokenFormComponent[];
1923

24+
export interface CustomizableSpokenFormComponentForType<T extends SpokenFormType> {
25+
type: "customizable";
26+
spokenForms: SpokenFormMapEntry;
27+
spokenFormType: T;
28+
id: SpokenFormMapKeyTypes[T];
29+
}
30+
2031
/**
2132
* A customizable spoken form component. This is a spoken form component that
2233
* can be customized by the user. It is used internally during spoken form
2334
* generation.
2435
*/
25-
export interface CustomizableSpokenFormComponent {
26-
type: "customizable";
27-
spokenForms: SpokenFormMapEntry;
28-
spokenFormType: SpokenFormType;
29-
id: string;
30-
}
36+
export type CustomizableSpokenFormComponent = {
37+
[K in SpokenFormType]: CustomizableSpokenFormComponentForType<K>;
38+
}[SpokenFormType];

packages/cursorless-engine/src/generateSpokenForm/defaultSpokenForms/modifiers.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { CompositeKeyMap } from "@cursorless/common";
22
import { SpeakableSurroundingPairName } from "../../SpokenFormMap";
33
import { SpokenFormComponentMap } from "../getSpokenFormComponentMap";
4-
import { CustomizableSpokenFormComponent } from "../SpokenFormComponent";
4+
import {
5+
CustomizableSpokenFormComponentForType,
6+
} from "../SpokenFormComponent";
57

68
const surroundingPairsDelimiters: Record<
79
SpeakableSurroundingPairName,
@@ -50,7 +52,7 @@ export function surroundingPairDelimitersToSpokenForm(
5052
spokenFormMap: SpokenFormComponentMap,
5153
left: string,
5254
right: string,
53-
): CustomizableSpokenFormComponent {
55+
): CustomizableSpokenFormComponentForType<"pairedDelimiter"> {
5456
const pairName = surroundingPairDelimiterToName.get([left, right]);
5557
if (pairName == null) {
5658
throw Error(`Unknown surrounding pair delimiters '${left} ${right}'`);

packages/cursorless-engine/src/generateSpokenForm/getSpokenFormComponentMap.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import {
22
PartialSpokenFormTypes,
33
SpokenFormMap,
44
SpokenFormMapKeyTypes,
5+
SpokenFormType,
56
} from "../SpokenFormMap";
6-
import { CustomizableSpokenFormComponent } from "./SpokenFormComponent";
7+
import { CustomizableSpokenFormComponentForType } from "./SpokenFormComponent";
78

89
/**
910
* A spoken form component map is a map of spoken form types to a map of IDs to
@@ -13,9 +14,21 @@ import { CustomizableSpokenFormComponent } from "./SpokenFormComponent";
1314
* generation.
1415
*/
1516
export type SpokenFormComponentMap = {
16-
readonly [K in keyof SpokenFormMapKeyTypes]: K extends PartialSpokenFormTypes
17-
? Partial<Record<SpokenFormMapKeyTypes[K], CustomizableSpokenFormComponent>>
18-
: Record<SpokenFormMapKeyTypes[K], CustomizableSpokenFormComponent>;
17+
readonly [K in SpokenFormType]: K extends PartialSpokenFormTypes
18+
? Readonly<
19+
Partial<
20+
Record<
21+
SpokenFormMapKeyTypes[K],
22+
CustomizableSpokenFormComponentForType<K>
23+
>
24+
>
25+
>
26+
: Readonly<
27+
Record<
28+
SpokenFormMapKeyTypes[K],
29+
CustomizableSpokenFormComponentForType<K>
30+
>
31+
>;
1932
};
2033

2134
/**

0 commit comments

Comments
 (0)