Skip to content

Commit

Permalink
Move custom Object.keys() overload to util
Browse files Browse the repository at this point in the history
  • Loading branch information
auscompgeek committed Nov 11, 2023
1 parent a79254c commit 9f73533
Show file tree
Hide file tree
Showing 9 changed files with 51 additions and 31 deletions.
2 changes: 1 addition & 1 deletion packages/cursorless-engine/src/languages/getNodeMatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export function getNodeMatcher(

export const languageMatchers: Record<
LegacyLanguageId,
Record<SimpleScopeTypeType, NodeMatcher>
Partial<Record<SimpleScopeTypeType, NodeMatcher>>
> = {
c: cpp,
cpp,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import {
ComplexSurroundingPairName,
SimpleSurroundingPairName,
} from "@cursorless/common";
import { unsafeKeys } from "../../../util/object";

type IndividualDelimiterText = string | string[];

export const delimiterToText: Record<
SimpleSurroundingPairName,
[IndividualDelimiterText, IndividualDelimiterText]
> = {
> = Object.freeze({
angleBrackets: [
["</", "<"],
[">", "/>"],
Expand All @@ -23,7 +24,7 @@ export const delimiterToText: Record<
parentheses: [["(", "$("], ")"],
singleQuotes: ["'", "'"],
squareBrackets: ["[", "]"],
};
});

export const leftToRightMap: Record<string, string> = Object.fromEntries(
Object.values(delimiterToText),
Expand All @@ -37,7 +38,7 @@ export const complexDelimiterMap: Record<
ComplexSurroundingPairName,
SimpleSurroundingPairName[]
> = {
any: Object.keys(delimiterToText),
any: unsafeKeys(delimiterToText),
string: ["singleQuotes", "doubleQuotes", "backtickQuotes"],
collectionBoundary: [
"parentheses",
Expand Down
3 changes: 2 additions & 1 deletion packages/cursorless-engine/src/runIntegrationTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { languageMatchers } from "./languages/getNodeMatcher";
import { TreeSitter } from "./typings/TreeSitter";
import { legacyLanguageIds } from "./languages/LegacyLanguageId";
import { LanguageDefinitions } from "./languages/LanguageDefinitions";
import { unsafeKeys } from "./util/object";

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

Object.keys(languageMatchers[languageId]).map((scopeTypeType) => {
unsafeKeys(languageMatchers[languageId]).map((scopeTypeType) => {
if (
languageDefinitions.get(languageId)?.getScopeHandler({
type: scopeTypeType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@ export function checkMarks(originalFixture: TestCaseFixtureLegacy): undefined {
...(originalFixture.marksToCheck ?? []),
];

const actualMarks = Object.keys(
originalFixture.initialState.marks ?? {},
) as string[];
const actualMarks = Object.keys(originalFixture.initialState.marks ?? {});

assert.deepStrictEqual(
uniq(actualMarks.map(normalizeGraphemes)).sort(),
Expand Down
3 changes: 2 additions & 1 deletion packages/cursorless-engine/src/testCaseRecorder/TestCase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { ide } from "../singletons/ide.singleton";
import { extractTargetKeys } from "../testUtil/extractTargetKeys";
import { takeSnapshot } from "../testUtil/takeSnapshot";
import { getPartialTargetDescriptors } from "../util/getPartialTargetDescriptors";
import { unsafeKeys } from "../util/object";

export class TestCase {
private languageId: string;
Expand Down Expand Up @@ -122,7 +123,7 @@ export class TestCase {
visibleRanges: !visibleRangeActions.includes(this.command.action.name),
};

return Object.keys(excludedFields).filter((field) => excludedFields[field]);
return unsafeKeys(excludedFields).filter((field) => excludedFields[field]);
}

toYaml() {
Expand Down
26 changes: 16 additions & 10 deletions packages/cursorless-engine/src/util/nodeMatchers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
simpleSelectionExtractor,
unwrapSelectionExtractor,
} from "./nodeSelectors";
import { unsafeKeys } from "./object";

export function matcher(
finder: NodeFinder,
Expand Down Expand Up @@ -177,14 +178,19 @@ export const notSupported: NodeMatcher = (

export function createPatternMatchers(
nodeMatchers: Partial<Record<SimpleScopeTypeType, NodeMatcherAlternative>>,
): Record<SimpleScopeTypeType, NodeMatcher> {
Object.keys(nodeMatchers).forEach((scopeType: SimpleScopeTypeType) => {
const matcher = nodeMatchers[scopeType];
if (Array.isArray(matcher)) {
nodeMatchers[scopeType] = patternMatcher(...matcher);
} else if (typeof matcher === "string") {
nodeMatchers[scopeType] = patternMatcher(matcher);
}
});
return nodeMatchers as Record<SimpleScopeTypeType, NodeMatcher>;
): Partial<Record<SimpleScopeTypeType, NodeMatcher>> {
return Object.freeze(
Object.fromEntries(
unsafeKeys(nodeMatchers).map((scopeType: SimpleScopeTypeType) => {
const matcher = nodeMatchers[scopeType];
if (Array.isArray(matcher)) {
return [scopeType, patternMatcher(...matcher)];
} else if (typeof matcher === "string") {
return [scopeType, patternMatcher(matcher)];
} else {
return [scopeType, matcher];
}
}),
),
);
}
24 changes: 24 additions & 0 deletions packages/cursorless-engine/src/util/object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,27 @@ export function mergeStrict<Value>(

return returnValue;
}

/**
* `Object.keys` but returns an array of the keys TypeScript knows about.
*
* Note that this is technically unsound, as TypeScript is a structural type system.
* Consider the following example (from ts-reset's docs):
* ```
* type Func = () => { id: string };
*
* const func: Func = () => {
* return {
* id: "123",
* // No error on an excess property!
* name: "Hello!",
* }
* };
* ```
*
* Consider only using this on objects frozen at construction time
* or locals that don't escape the calling scope.
*/
export function unsafeKeys<T extends object>(o: T): (keyof T)[] {
return Object.keys(o) as (keyof T)[];
}
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ async function runTest(file: string, spyIde: SpyIDE) {
? undefined
: marksToPlainObject(
extractTargetedMarks(
Object.keys(fixture.finalState.marks) as string[],
Object.keys(fixture.finalState.marks),
readableHatMap,
),
);
Expand Down
11 changes: 0 additions & 11 deletions typings/object.d.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,4 @@
// From https://fettblog.eu/typescript-better-object-keys/
type ObjectKeys<T> = T extends object
? (keyof T)[]
: T extends number
? []
: T extends Array<any> | string
? string[]
: never;

interface ObjectConstructor {
keys<T>(o: T): ObjectKeys<T>;

fromEntries<
V extends PropertyKey,
T extends [readonly [V, any]] | Array<readonly [V, any]>,
Expand Down

0 comments on commit 9f73533

Please sign in to comment.