From 9f7353373f69caea1a972d690b566ca11df6b72b Mon Sep 17 00:00:00 2001 From: David Vo Date: Sat, 11 Nov 2023 23:49:49 +1100 Subject: [PATCH] Move custom Object.keys() overload to util --- .../src/languages/getNodeMatcher.ts | 2 +- .../surroundingPair/delimiterMaps.ts | 7 ++--- .../src/runIntegrationTests.ts | 3 ++- .../transformRecordedTests/checkMarks.ts | 4 +-- .../src/testCaseRecorder/TestCase.ts | 3 ++- .../src/util/nodeMatchers.ts | 26 ++++++++++++------- packages/cursorless-engine/src/util/object.ts | 24 +++++++++++++++++ .../src/suite/recorded.vscode.test.ts | 2 +- typings/object.d.ts | 11 -------- 9 files changed, 51 insertions(+), 31 deletions(-) diff --git a/packages/cursorless-engine/src/languages/getNodeMatcher.ts b/packages/cursorless-engine/src/languages/getNodeMatcher.ts index 39e0b1b22d..ac5f5eea0a 100644 --- a/packages/cursorless-engine/src/languages/getNodeMatcher.ts +++ b/packages/cursorless-engine/src/languages/getNodeMatcher.ts @@ -52,7 +52,7 @@ export function getNodeMatcher( export const languageMatchers: Record< LegacyLanguageId, - Record + Partial> > = { c: cpp, cpp, diff --git a/packages/cursorless-engine/src/processTargets/modifiers/surroundingPair/delimiterMaps.ts b/packages/cursorless-engine/src/processTargets/modifiers/surroundingPair/delimiterMaps.ts index b8e54be1fe..e5d42f8d0a 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/surroundingPair/delimiterMaps.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/surroundingPair/delimiterMaps.ts @@ -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: [ ["", "/>"], @@ -23,7 +24,7 @@ export const delimiterToText: Record< parentheses: [["(", "$("], ")"], singleQuotes: ["'", "'"], squareBrackets: ["[", "]"], -}; +}); export const leftToRightMap: Record = Object.fromEntries( Object.values(delimiterToText), @@ -37,7 +38,7 @@ export const complexDelimiterMap: Record< ComplexSurroundingPairName, SimpleSurroundingPairName[] > = { - any: Object.keys(delimiterToText), + any: unsafeKeys(delimiterToText), string: ["singleQuotes", "doubleQuotes", "backtickQuotes"], collectionBoundary: [ "parentheses", diff --git a/packages/cursorless-engine/src/runIntegrationTests.ts b/packages/cursorless-engine/src/runIntegrationTests.ts index 5b5c541a85..855f433331 100644 --- a/packages/cursorless-engine/src/runIntegrationTests.ts +++ b/packages/cursorless-engine/src/runIntegrationTests.ts @@ -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 @@ -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, diff --git a/packages/cursorless-engine/src/scripts/transformRecordedTests/checkMarks.ts b/packages/cursorless-engine/src/scripts/transformRecordedTests/checkMarks.ts index e0e5dc0196..bcff07df9d 100644 --- a/packages/cursorless-engine/src/scripts/transformRecordedTests/checkMarks.ts +++ b/packages/cursorless-engine/src/scripts/transformRecordedTests/checkMarks.ts @@ -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(), diff --git a/packages/cursorless-engine/src/testCaseRecorder/TestCase.ts b/packages/cursorless-engine/src/testCaseRecorder/TestCase.ts index d1802db55e..04b85fa684 100644 --- a/packages/cursorless-engine/src/testCaseRecorder/TestCase.ts +++ b/packages/cursorless-engine/src/testCaseRecorder/TestCase.ts @@ -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; @@ -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() { diff --git a/packages/cursorless-engine/src/util/nodeMatchers.ts b/packages/cursorless-engine/src/util/nodeMatchers.ts index 550fd9b01c..5caf4cdf1b 100644 --- a/packages/cursorless-engine/src/util/nodeMatchers.ts +++ b/packages/cursorless-engine/src/util/nodeMatchers.ts @@ -21,6 +21,7 @@ import { simpleSelectionExtractor, unwrapSelectionExtractor, } from "./nodeSelectors"; +import { unsafeKeys } from "./object"; export function matcher( finder: NodeFinder, @@ -177,14 +178,19 @@ export const notSupported: NodeMatcher = ( export function createPatternMatchers( nodeMatchers: Partial>, -): Record { - 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; +): Partial> { + 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]; + } + }), + ), + ); } diff --git a/packages/cursorless-engine/src/util/object.ts b/packages/cursorless-engine/src/util/object.ts index edc5bfe2c3..c63808be91 100644 --- a/packages/cursorless-engine/src/util/object.ts +++ b/packages/cursorless-engine/src/util/object.ts @@ -20,3 +20,27 @@ export function mergeStrict( 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(o: T): (keyof T)[] { + return Object.keys(o) as (keyof T)[]; +} diff --git a/packages/cursorless-vscode-e2e/src/suite/recorded.vscode.test.ts b/packages/cursorless-vscode-e2e/src/suite/recorded.vscode.test.ts index 62ad7e84c1..14cb13c9c1 100644 --- a/packages/cursorless-vscode-e2e/src/suite/recorded.vscode.test.ts +++ b/packages/cursorless-vscode-e2e/src/suite/recorded.vscode.test.ts @@ -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, ), ); diff --git a/typings/object.d.ts b/typings/object.d.ts index 8f94f31e6d..3ca52f5b38 100644 --- a/typings/object.d.ts +++ b/typings/object.d.ts @@ -1,15 +1,4 @@ -// From https://fettblog.eu/typescript-better-object-keys/ -type ObjectKeys = T extends object - ? (keyof T)[] - : T extends number - ? [] - : T extends Array | string - ? string[] - : never; - interface ObjectConstructor { - keys(o: T): ObjectKeys; - fromEntries< V extends PropertyKey, T extends [readonly [V, any]] | Array,