Skip to content

Commit 9f73533

Browse files
committed
Move custom Object.keys() overload to util
1 parent a79254c commit 9f73533

File tree

9 files changed

+51
-31
lines changed

9 files changed

+51
-31
lines changed

packages/cursorless-engine/src/languages/getNodeMatcher.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export function getNodeMatcher(
5252

5353
export const languageMatchers: Record<
5454
LegacyLanguageId,
55-
Record<SimpleScopeTypeType, NodeMatcher>
55+
Partial<Record<SimpleScopeTypeType, NodeMatcher>>
5656
> = {
5757
c: cpp,
5858
cpp,

packages/cursorless-engine/src/processTargets/modifiers/surroundingPair/delimiterMaps.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ import {
22
ComplexSurroundingPairName,
33
SimpleSurroundingPairName,
44
} from "@cursorless/common";
5+
import { unsafeKeys } from "../../../util/object";
56

67
type IndividualDelimiterText = string | string[];
78

89
export const delimiterToText: Record<
910
SimpleSurroundingPairName,
1011
[IndividualDelimiterText, IndividualDelimiterText]
11-
> = {
12+
> = Object.freeze({
1213
angleBrackets: [
1314
["</", "<"],
1415
[">", "/>"],
@@ -23,7 +24,7 @@ export const delimiterToText: Record<
2324
parentheses: [["(", "$("], ")"],
2425
singleQuotes: ["'", "'"],
2526
squareBrackets: ["[", "]"],
26-
};
27+
});
2728

2829
export const leftToRightMap: Record<string, string> = Object.fromEntries(
2930
Object.values(delimiterToText),
@@ -37,7 +38,7 @@ export const complexDelimiterMap: Record<
3738
ComplexSurroundingPairName,
3839
SimpleSurroundingPairName[]
3940
> = {
40-
any: Object.keys(delimiterToText),
41+
any: unsafeKeys(delimiterToText),
4142
string: ["singleQuotes", "doubleQuotes", "backtickQuotes"],
4243
collectionBoundary: [
4344
"parentheses",

packages/cursorless-engine/src/runIntegrationTests.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { languageMatchers } from "./languages/getNodeMatcher";
33
import { TreeSitter } from "./typings/TreeSitter";
44
import { legacyLanguageIds } from "./languages/LegacyLanguageId";
55
import { LanguageDefinitions } from "./languages/LanguageDefinitions";
6+
import { unsafeKeys } from "./util/object";
67

78
/**
89
* Run tests that require multiple components to be instantiated, as well as a
@@ -27,7 +28,7 @@ async function assertNoScopesBothLegacyAndNew(
2728
for (const languageId of legacyLanguageIds) {
2829
await treeSitter.loadLanguage(languageId);
2930

30-
Object.keys(languageMatchers[languageId]).map((scopeTypeType) => {
31+
unsafeKeys(languageMatchers[languageId]).map((scopeTypeType) => {
3132
if (
3233
languageDefinitions.get(languageId)?.getScopeHandler({
3334
type: scopeTypeType,

packages/cursorless-engine/src/scripts/transformRecordedTests/checkMarks.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,7 @@ export function checkMarks(originalFixture: TestCaseFixtureLegacy): undefined {
2727
...(originalFixture.marksToCheck ?? []),
2828
];
2929

30-
const actualMarks = Object.keys(
31-
originalFixture.initialState.marks ?? {},
32-
) as string[];
30+
const actualMarks = Object.keys(originalFixture.initialState.marks ?? {});
3331

3432
assert.deepStrictEqual(
3533
uniq(actualMarks.map(normalizeGraphemes)).sort(),

packages/cursorless-engine/src/testCaseRecorder/TestCase.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { ide } from "../singletons/ide.singleton";
2323
import { extractTargetKeys } from "../testUtil/extractTargetKeys";
2424
import { takeSnapshot } from "../testUtil/takeSnapshot";
2525
import { getPartialTargetDescriptors } from "../util/getPartialTargetDescriptors";
26+
import { unsafeKeys } from "../util/object";
2627

2728
export class TestCase {
2829
private languageId: string;
@@ -122,7 +123,7 @@ export class TestCase {
122123
visibleRanges: !visibleRangeActions.includes(this.command.action.name),
123124
};
124125

125-
return Object.keys(excludedFields).filter((field) => excludedFields[field]);
126+
return unsafeKeys(excludedFields).filter((field) => excludedFields[field]);
126127
}
127128

128129
toYaml() {

packages/cursorless-engine/src/util/nodeMatchers.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
simpleSelectionExtractor,
2222
unwrapSelectionExtractor,
2323
} from "./nodeSelectors";
24+
import { unsafeKeys } from "./object";
2425

2526
export function matcher(
2627
finder: NodeFinder,
@@ -177,14 +178,19 @@ export const notSupported: NodeMatcher = (
177178

178179
export function createPatternMatchers(
179180
nodeMatchers: Partial<Record<SimpleScopeTypeType, NodeMatcherAlternative>>,
180-
): Record<SimpleScopeTypeType, NodeMatcher> {
181-
Object.keys(nodeMatchers).forEach((scopeType: SimpleScopeTypeType) => {
182-
const matcher = nodeMatchers[scopeType];
183-
if (Array.isArray(matcher)) {
184-
nodeMatchers[scopeType] = patternMatcher(...matcher);
185-
} else if (typeof matcher === "string") {
186-
nodeMatchers[scopeType] = patternMatcher(matcher);
187-
}
188-
});
189-
return nodeMatchers as Record<SimpleScopeTypeType, NodeMatcher>;
181+
): Partial<Record<SimpleScopeTypeType, NodeMatcher>> {
182+
return Object.freeze(
183+
Object.fromEntries(
184+
unsafeKeys(nodeMatchers).map((scopeType: SimpleScopeTypeType) => {
185+
const matcher = nodeMatchers[scopeType];
186+
if (Array.isArray(matcher)) {
187+
return [scopeType, patternMatcher(...matcher)];
188+
} else if (typeof matcher === "string") {
189+
return [scopeType, patternMatcher(matcher)];
190+
} else {
191+
return [scopeType, matcher];
192+
}
193+
}),
194+
),
195+
);
190196
}

packages/cursorless-engine/src/util/object.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,27 @@ export function mergeStrict<Value>(
2020

2121
return returnValue;
2222
}
23+
24+
/**
25+
* `Object.keys` but returns an array of the keys TypeScript knows about.
26+
*
27+
* Note that this is technically unsound, as TypeScript is a structural type system.
28+
* Consider the following example (from ts-reset's docs):
29+
* ```
30+
* type Func = () => { id: string };
31+
*
32+
* const func: Func = () => {
33+
* return {
34+
* id: "123",
35+
* // No error on an excess property!
36+
* name: "Hello!",
37+
* }
38+
* };
39+
* ```
40+
*
41+
* Consider only using this on objects frozen at construction time
42+
* or locals that don't escape the calling scope.
43+
*/
44+
export function unsafeKeys<T extends object>(o: T): (keyof T)[] {
45+
return Object.keys(o) as (keyof T)[];
46+
}

packages/cursorless-vscode-e2e/src/suite/recorded.vscode.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ async function runTest(file: string, spyIde: SpyIDE) {
159159
? undefined
160160
: marksToPlainObject(
161161
extractTargetedMarks(
162-
Object.keys(fixture.finalState.marks) as string[],
162+
Object.keys(fixture.finalState.marks),
163163
readableHatMap,
164164
),
165165
);

typings/object.d.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,4 @@
1-
// From https://fettblog.eu/typescript-better-object-keys/
2-
type ObjectKeys<T> = T extends object
3-
? (keyof T)[]
4-
: T extends number
5-
? []
6-
: T extends Array<any> | string
7-
? string[]
8-
: never;
9-
101
interface ObjectConstructor {
11-
keys<T>(o: T): ObjectKeys<T>;
12-
132
fromEntries<
143
V extends PropertyKey,
154
T extends [readonly [V, any]] | Array<readonly [V, any]>,

0 commit comments

Comments
 (0)