From 938370924a6c902741e4d8b2db06a3c11d94d366 Mon Sep 17 00:00:00 2001 From: Pelle Wessman Date: Tue, 5 Mar 2024 01:07:13 +0100 Subject: [PATCH 1/2] Improve generation and what is included in package --- package.json | 13 +++++++------ rollup.config.js | 4 +++- tsconfig.json | 4 +++- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 1e27f09..b289553 100644 --- a/package.json +++ b/package.json @@ -16,9 +16,10 @@ "./package.json": "./package.json" }, "files": [ - "dist/index.d.ts", - "dist/visitor-keys.d.ts", + "dist/*.d.ts", + "dist/*.d.ts.map", "dist/eslint-visitor-keys.cjs", + "dist/eslint-visitor-keys.cjs.map", "dist/eslint-visitor-keys.d.cts", "lib" ], @@ -39,10 +40,10 @@ "json-diff": "^0.7.3", "mocha": "^9.2.1", "opener": "^1.5.2", - "rollup": "^2.70.0", - "rollup-plugin-dts": "^4.2.3", - "tsd": "^0.19.1", - "typescript": "^4.6.2" + "rollup": "^4.12.0", + "rollup-plugin-dts": "^6.1.0", + "tsd": "^0.30.7", + "typescript": "^5.3.3" }, "scripts": { "build": "npm run build:cjs && npm run build:types", diff --git a/rollup.config.js b/rollup.config.js index 9f627c9..7a1a0c0 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -6,7 +6,9 @@ export default [ treeshake: false, output: { format: "cjs", - file: "dist/eslint-visitor-keys.cjs" + file: "dist/eslint-visitor-keys.cjs", + sourcemap: true, + sourcemapExcludeSources: true } }, { diff --git a/tsconfig.json b/tsconfig.json index 5afc060..5167231 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,7 +12,9 @@ "emitDeclarationOnly": true, "strict": true, "target": "es6", - "outDir": "dist" + "outDir": "dist", + "removeComments": true, + "stripInternal": true }, "include": ["lib/**/*.js"], "exclude": ["node_modules"] From 1eed2205f5215b46a590004b11a836e9ad6ff05e Mon Sep 17 00:00:00 2001 From: Pelle Wessman Date: Tue, 5 Mar 2024 00:50:25 +0100 Subject: [PATCH 2/2] Improved types --- README.md | 59 +++++++++++++++++++++++++++++--- lib/index.js | 67 +++++++++++++++++++++++++------------ lib/types.js | 12 +++++++ lib/visitor-keys.js | 28 ++++++---------- test-d/index.test-d.ts | 55 +++++++++++++++++++----------- tools/build-keys-from-ts.js | 20 +++++------ tsconfig.json | 2 +- 7 files changed, 168 insertions(+), 75 deletions(-) create mode 100644 lib/types.js diff --git a/README.md b/README.md index d5c13a3..c847555 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ const evk = require("eslint-visitor-keys") ### evk.KEYS -> type: `{ [type: string]: string[] | undefined }` +> type: `VisitorKeys` Visitor keys. This keys are frozen. @@ -49,7 +49,7 @@ console.log(evk.KEYS.AssignmentExpression) // → ["left", "right"] ### evk.getKeys(node) -> type: `(node: object) => string[]` +> type: `(node: object) => FilteredKeysOf[]` Get the visitor keys of a given AST node. @@ -61,16 +61,17 @@ For example: ```js const node = { + _something: true, type: "AssignmentExpression", left: { type: "Identifier", name: "foo" }, - right: { type: "Literal", value: 0 } + right: { type: "Literal", value: 0 }, } console.log(evk.getKeys(node)) // → ["type", "left", "right"] ``` ### evk.unionWith(additionalKeys) -> type: `(additionalKeys: object) => { [type: string]: string[] | undefined }` +> type: `(additionalKeys: object) => VisitorKeys` Make the union set with `evk.KEYS` and the given keys. @@ -85,6 +86,56 @@ console.log(evk.unionWith({ })) // → { ..., MethodDefinition: ["decorators", "key", "value"], ... } ``` +## Types + +### VisitorKeyTypes + +A union of string literals representing all the keys on `evk.KEYS`: + +``` +"ArrayExpression" | "ArrayPattern" | ... +``` + +### VisitorKeys + +Defines the base shape of `evk.KEYS` style objects. + +Takes two optional inputs: `VisitorKeys` + +* `NodeTypes` makes up the keys of `VisitorKeys`, should be the type of [ESTree] nodes +* `Node` represents the [ESTree] nodes themselves and is used to calculate possible values of `VisitorKeys`, an array of property names which have child nodes + +Default inputs: `VisitorKeys>` + +Example: The type of `evk.KEYS` is roughly equivalent to: + +```ts +VisitorKeys +``` + +### FilteredKeysOf\ + +Similar to `keyof T` but: + +* Filters away keys just like `evk.getKeys` does +* Rather than only returning commons keys when `T` is a union it instead returns all keys + +Example: + +```ts +FilteredKeysOf< + { parent: 123, abc: true, xyz: true }, + { parent: 456, abc: true, def: true }, +> +``` + +equals + +```ts +'abc' | 'xyz' | +``` + + ## 📰 Change log See [GitHub releases](https://github.com/eslint/eslint-visitor-keys/releases). diff --git a/lib/index.js b/lib/index.js index b4bb643..d4558fa 100644 --- a/lib/index.js +++ b/lib/index.js @@ -5,53 +5,78 @@ import KEYS from "./visitor-keys.js"; /** - * @typedef {import('./visitor-keys.js').VisitorKeys} VisitorKeys + * @typedef {import('./visitor-keys.js').VisitorKeyTypes} VisitorKeyTypes + */ + +/* eslint-disable jsdoc/valid-types -- jsdoc-type-pratt-parser can't parse template literal types */ +/** + * @template T + * @typedef {Exclude, string>, KEY_FILTER[number] | `_${string}`>} FilteredKeysOf + */ +/* eslint-enable jsdoc/valid-types -- jsdoc-type-pratt-parser can't parse template literal types */ + +/** + * @template {string} [NodeTypes=string] + * @template {object} [Node=Record] + * @typedef {Readonly>>>} VisitorKeys */ // List to ignore keys. -const KEY_BLACKLIST = new Set([ +const KEY_FILTER = /** @type {const} */ ([ "parent", "leadingComments", "trailingComments" ]); +/* eslint-disable jsdoc/valid-types -- jsdoc-type-pratt-parser can't parse template literal types */ /** * Check whether a given key should be used or not. - * @param {string} key The key to check. - * @returns {boolean} `true` if the key should be used. + * @template {string|number|symbol} K + * @template {string} T + * @param {K|T} key The key to check. + * @param {ReadonlyArray} filterlist The list of keys to filter out. + * @returns {key is Exclude, T | `_${string}`>} `true` if the key should be used. */ -function filterKey(key) { - return !KEY_BLACKLIST.has(key) && key[0] !== "_"; +function isFiltered(key, filterlist) { + return !filterlist.includes(/** @type {T} */ (key)) && typeof key === "string" && key[0] !== "_"; } +/* eslint-enable jsdoc/valid-types -- jsdoc-type-pratt-parser can't parse template literal types */ - -/* eslint-disable jsdoc/valid-types -- doesn't allow `readonly`. - TODO: remove eslint-disable when https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/164 is fixed -*/ /** * Get visitor keys of a given node. - * @param {object} node The AST node to get keys. - * @returns {readonly string[]} Visitor keys of the node. + * @template {object} T + * @param {T} node The AST node to get keys. + * @returns {ReadonlyArray>} Visitor keys of the node. */ export function getKeys(node) { - return Object.keys(node).filter(filterKey); + + /** @type {FilteredKeysOf[]} */ + const result = []; + + for (const key of /** @type {import('./types.js').KeyOfUnion[]} */ (Object.keys(node))) { + if (isFiltered(key, KEY_FILTER)) { + result.push(key); + } + } + return result; } -/* eslint-enable jsdoc/valid-types -- doesn't allow `readonly` */ /** * Make the union set with `KEYS` and given keys. - * @param {VisitorKeys} additionalKeys The additional keys. - * @returns {VisitorKeys} The union set. + * @template {VisitorKeys} T + * @param {T} additionalKeys The additional keys. + * @returns {VisitorKeys>} The union set. */ export function unionWith(additionalKeys) { - const retv = /** @type {{ [type: string]: ReadonlyArray }} */ - (Object.assign({}, KEYS)); - for (const type of Object.keys(additionalKeys)) { - if (Object.prototype.hasOwnProperty.call(retv, type)) { + /** @type {Record>} */ + const retv = { ...additionalKeys, ...KEYS }; + + for (const type of /** @type {Array} */ (Object.keys(additionalKeys))) { + if (Object.hasOwn(KEYS, type)) { const keys = new Set(additionalKeys[type]); - for (const key of retv[type]) { + for (const key of KEYS[/** @type {VisitorKeyTypes} */ (type)]) { keys.add(key); } diff --git a/lib/types.js b/lib/types.js new file mode 100644 index 0000000..d20307f --- /dev/null +++ b/lib/types.js @@ -0,0 +1,12 @@ +/* eslint-disable jsdoc/valid-types -- jsdoc-type-pratt-parser can't parse conditional types */ + +/** + * Added here to avoid it being exported in the generated types + * @see https://github.com/sindresorhus/type-fest/blob/906e7e77204c65f7512f9f54b3205f25c5c0c8e5/source/keys-of-union.d.ts#L38-L40 + * @template T + * @typedef {T extends unknown ? keyof T : never} KeyOfUnion + */ + +/* eslint-enable jsdoc/valid-types -- jsdoc-type-pratt-parser can't parse conditional types */ + +export default {}; diff --git a/lib/visitor-keys.js b/lib/visitor-keys.js index f54a347..ca16c12 100644 --- a/lib/visitor-keys.js +++ b/lib/visitor-keys.js @@ -1,15 +1,10 @@ -/* eslint-disable jsdoc/valid-types -- doesn't allow `readonly`. - TODO: remove eslint-disable when https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/164 is fixed -*/ -/** - * @typedef {{ readonly [type: string]: ReadonlyArray }} VisitorKeys - */ -/* eslint-enable jsdoc/valid-types -- doesn't allow `readonly string[]`. TODO: check why */ +// DO NOT MODIFY - generated by ../tools/build-keys-from-ts.js -/** - * @type {VisitorKeys} - */ -const KEYS = { +/** @typedef {"ArrayExpression" | "ArrayPattern" | "ArrowFunctionExpression" | "AssignmentExpression" | "AssignmentPattern" | "AwaitExpression" | "BinaryExpression" | "BlockStatement" | "BreakStatement" | "CallExpression" | "CatchClause" | "ChainExpression" | "ClassBody" | "ClassDeclaration" | "ClassExpression" | "ConditionalExpression" | "ContinueStatement" | "DebuggerStatement" | "DoWhileStatement" | "EmptyStatement" | "ExperimentalRestProperty" | "ExperimentalSpreadProperty" | "ExportAllDeclaration" | "ExportDefaultDeclaration" | "ExportNamedDeclaration" | "ExportSpecifier" | "ExpressionStatement" | "ForInStatement" | "ForOfStatement" | "ForStatement" | "FunctionDeclaration" | "FunctionExpression" | "Identifier" | "IfStatement" | "ImportDeclaration" | "ImportDefaultSpecifier" | "ImportExpression" | "ImportNamespaceSpecifier" | "ImportSpecifier" | "JSXAttribute" | "JSXClosingElement" | "JSXClosingFragment" | "JSXElement" | "JSXEmptyExpression" | "JSXExpressionContainer" | "JSXFragment" | "JSXIdentifier" | "JSXMemberExpression" | "JSXNamespacedName" | "JSXOpeningElement" | "JSXOpeningFragment" | "JSXSpreadAttribute" | "JSXSpreadChild" | "JSXText" | "LabeledStatement" | "Literal" | "LogicalExpression" | "MemberExpression" | "MetaProperty" | "MethodDefinition" | "NewExpression" | "ObjectExpression" | "ObjectPattern" | "PrivateIdentifier" | "Program" | "Property" | "PropertyDefinition" | "RestElement" | "ReturnStatement" | "SequenceExpression" | "SpreadElement" | "StaticBlock" | "Super" | "SwitchCase" | "SwitchStatement" | "TaggedTemplateExpression" | "TemplateElement" | "TemplateLiteral" | "ThisExpression" | "ThrowStatement" | "TryStatement" | "UnaryExpression" | "UpdateExpression" | "VariableDeclaration" | "VariableDeclarator" | "WhileStatement" | "WithStatement" | "YieldExpression"} VisitorKeyTypes */ + +/** @typedef {Readonly<{ [type: string]: ReadonlyArray }>} VisitorKeys */ + +const KEYS = /** @type {const} */ (/** @satisfies {VisitorKeys} */ ({ ArrayExpression: [ "elements" ], @@ -305,14 +300,11 @@ const KEYS = { YieldExpression: [ "argument" ] -}; - -// Types. -const NODE_TYPES = Object.keys(KEYS); +})); -// Freeze the keys. -for (const type of NODE_TYPES) { - Object.freeze(KEYS[type]); +// Freeze the object values. +for (const value of Object.values(KEYS)) { + Object.freeze(value); } Object.freeze(KEYS); diff --git a/test-d/index.test-d.ts b/test-d/index.test-d.ts index 8ff6936..395292d 100644 --- a/test-d/index.test-d.ts +++ b/test-d/index.test-d.ts @@ -1,9 +1,12 @@ import { expectType, expectAssignable, expectError } from 'tsd'; -import { KEYS, getKeys, unionWith, VisitorKeys } from "../"; +import { KEYS, getKeys, unionWith, VisitorKeys, VisitorKeyTypes } from "../"; +import { KeyOfUnion } from '../lib/types'; const assignmentExpression = { + _something: true, type: "AssignmentExpression", + parent: 'abc', operator: "=", left: { type: "Identifier", @@ -25,30 +28,44 @@ const assignmentExpression = { range: [ 0, 5 - ] + ], + leadingComments: 'abc', + trailingComments: 'abc' }; -expectType<{readonly [type: string]: readonly string[]}>(KEYS); +const readOnlyStringKeyedObject: { + readonly [type: string]: readonly string[] +} = { + TestInterface1: ["left", "right"], + TestInterface2: ["expression"] +}; + +expectAssignable<{readonly [type: string]: readonly string[]}>(KEYS); +expectAssignable(KEYS); +expectAssignable>(KEYS); +expectAssignable | KeyOfUnion>>>(KEYS); + +expectType(KEYS.ArrayPattern); -expectType(getKeys(assignmentExpression)); +expectType(getKeys(assignmentExpression)); +expectAssignable(getKeys(assignmentExpression)); +expectType(getKeys(readOnlyStringKeyedObject)); -expectType<{readonly [type: string]: readonly string[]}>(unionWith({ +expectType>>(unionWith({ TestInterface1: ["left", "right"], TestInterface2: ["expression"] })); +expectAssignable<{readonly [type: string]: readonly string[]}>(unionWith({ + TestInterface1: ["left", "right"], + TestInterface2: ["expression"] +})); +expectType>>(unionWith(readOnlyStringKeyedObject)); -const readonlyKeys: { - readonly [type: string]: readonly string[] -} = { - TestInterface1: ["left", "right"] -}; - -expectAssignable(readonlyKeys); +expectAssignable(readOnlyStringKeyedObject); -// https://github.com/SamVerschueren/tsd/issues/143 -// expectError(() => { -// const erring: VisitorKeys = { -// TestInterface1: ["left", "right"] -// }; -// erring.TestInterface1 = ["badAttemptOverwrite"]; -// }); +expectError(() => { + const erring: VisitorKeys = { + TestInterface1: ["left", "right"] + }; + erring.TestInterface1 = ["badAttemptOverwrite"]; +}); diff --git a/tools/build-keys-from-ts.js b/tools/build-keys-from-ts.js index b644c50..1367b3c 100644 --- a/tools/build-keys-from-ts.js +++ b/tools/build-keys-from-ts.js @@ -30,21 +30,17 @@ const { promises: { writeFile } } = fs; writeFile( "./lib/visitor-keys.js", // eslint-disable-next-line indent -- Readability -`/** - * @typedef {{ readonly [type: string]: ReadonlyArray }} VisitorKeys - */ +`// DO NOT MODIFY - generated by ../tools/build-keys-from-ts.js -/** - * @type {VisitorKeys} - */ -const KEYS = ${JSON.stringify(mergedKeys, null, 4).replace(/"(.*?)":/gu, "$1:")}; +/** @typedef {${Object.keys(mergedKeys).map(key => JSON.stringify(key)).join(" | ")}} VisitorKeyTypes */ + +/** @typedef {Readonly<{ [type: string]: ReadonlyArray }>} VisitorKeys */ -// Types. -const NODE_TYPES = Object.keys(KEYS); +const KEYS = /** @type {const} */ (/** @satisfies {VisitorKeys} */ (${JSON.stringify(mergedKeys, null, 4).replace(/"(.*?)":/gu, "$1:")})); -// Freeze the keys. -for (const type of NODE_TYPES) { - Object.freeze(KEYS[type]); +// Freeze the object values. +for (const value of Object.values(KEYS)) { + Object.freeze(value); } Object.freeze(KEYS); diff --git a/tsconfig.json b/tsconfig.json index 5167231..61dcde1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "lib": ["es2020"], + "lib": ["es2022"], "moduleResolution": "node", "module": "esnext", "resolveJsonModule": true,