diff --git a/package-lock.json b/package-lock.json index c519e0f..bae830f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,77 +1,78 @@ { "name": "estree-walker", "version": "3.0.2", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "estree-walker", "version": "3.0.2", "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + }, "devDependencies": { - "@types/estree": "0.0.42", - "typescript": "^3.7.5", + "typescript": "^4.9.0", "uvu": "^0.5.1" } }, "node_modules/@types/estree": { - "version": "0.0.42", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.42.tgz", - "integrity": "sha512-K1DPVvnBCPxzD+G51/cxVIoc2X8uUVl1zpJeE6iKcgHMj4+tbat5Xu4TjV7v2QSDbIeAfLi2hIk+u2+s0MlpUQ==", - "dev": true + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", + "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==" }, "node_modules/dequal": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.2.tgz", - "integrity": "sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", "dev": true, "engines": { "node": ">=6" } }, + "node_modules/diff": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/kleur": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.3.tgz", - "integrity": "sha512-H1tr8QP2PxFTNwAFM74Mui2b6ovcY9FoxJefgrwxY+OCJcq01k5nvhf4M/KnizzrJvLRap5STUy7dgDV35iUBw==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", "dev": true, "engines": { "node": ">=6" } }, "node_modules/mri": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.6.tgz", - "integrity": "sha512-oi1b3MfbyGa7FJMP9GmLTttni5JoICpYBRlq+x5V16fZbLsnL9N3wFqqIm/nIG43FjUFkFh9Epzp/kzUGUnJxQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", "dev": true, "engines": { "node": ">=4" } }, "node_modules/sade": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/sade/-/sade-1.7.4.tgz", - "integrity": "sha512-y5yauMD93rX840MwUJr7C1ysLFBgMspsdTo4UVrDg3fXDvtwOyIqykhVAAm6fk/3au77773itJStObgK+LKaiA==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", "dev": true, "dependencies": { "mri": "^1.1.0" }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/totalist": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-2.0.0.tgz", - "integrity": "sha512-+Y17F0YzxfACxTyjfhnJQEe7afPA0GSpYlFkl2VFMxYP7jshQf9gXV7cH47EfToBumFThfKBvfAcoUn6fdNeRQ==", - "dev": true, "engines": { "node": ">=6" } }, "node_modules/typescript": { - "version": "3.7.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.5.tgz", - "integrity": "sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==", + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", + "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -82,16 +83,15 @@ } }, "node_modules/uvu": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.1.tgz", - "integrity": "sha512-JGxttnOGDFs77FaZ0yMUHIzczzQ5R1IlDeNW6Wymw6gAscwMdAffVOP6TlxLIfReZyK8tahoGwWZaTCJzNFDkg==", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", + "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", "dev": true, "dependencies": { "dequal": "^2.0.0", "diff": "^5.0.0", "kleur": "^4.0.3", - "sade": "^1.7.3", - "totalist": "^2.0.0" + "sade": "^1.7.3" }, "bin": { "uvu": "bin.js" @@ -99,83 +99,6 @@ "engines": { "node": ">=8" } - }, - "node_modules/uvu/node_modules/diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - } - }, - "dependencies": { - "@types/estree": { - "version": "0.0.42", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.42.tgz", - "integrity": "sha512-K1DPVvnBCPxzD+G51/cxVIoc2X8uUVl1zpJeE6iKcgHMj4+tbat5Xu4TjV7v2QSDbIeAfLi2hIk+u2+s0MlpUQ==", - "dev": true - }, - "dequal": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.2.tgz", - "integrity": "sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug==", - "dev": true - }, - "kleur": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.3.tgz", - "integrity": "sha512-H1tr8QP2PxFTNwAFM74Mui2b6ovcY9FoxJefgrwxY+OCJcq01k5nvhf4M/KnizzrJvLRap5STUy7dgDV35iUBw==", - "dev": true - }, - "mri": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.6.tgz", - "integrity": "sha512-oi1b3MfbyGa7FJMP9GmLTttni5JoICpYBRlq+x5V16fZbLsnL9N3wFqqIm/nIG43FjUFkFh9Epzp/kzUGUnJxQ==", - "dev": true - }, - "sade": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/sade/-/sade-1.7.4.tgz", - "integrity": "sha512-y5yauMD93rX840MwUJr7C1ysLFBgMspsdTo4UVrDg3fXDvtwOyIqykhVAAm6fk/3au77773itJStObgK+LKaiA==", - "dev": true, - "requires": { - "mri": "^1.1.0" - } - }, - "totalist": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-2.0.0.tgz", - "integrity": "sha512-+Y17F0YzxfACxTyjfhnJQEe7afPA0GSpYlFkl2VFMxYP7jshQf9gXV7cH47EfToBumFThfKBvfAcoUn6fdNeRQ==", - "dev": true - }, - "typescript": { - "version": "3.7.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.5.tgz", - "integrity": "sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==", - "dev": true - }, - "uvu": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.1.tgz", - "integrity": "sha512-JGxttnOGDFs77FaZ0yMUHIzczzQ5R1IlDeNW6Wymw6gAscwMdAffVOP6TlxLIfReZyK8tahoGwWZaTCJzNFDkg==", - "dev": true, - "requires": { - "dequal": "^2.0.0", - "diff": "^5.0.0", - "kleur": "^4.0.3", - "sade": "^1.7.3", - "totalist": "^2.0.0" - }, - "dependencies": { - "diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true - } - } } } } diff --git a/package.json b/package.json index 65531a6..429eb4d 100644 --- a/package.json +++ b/package.json @@ -23,9 +23,11 @@ "prepublishOnly": "tsc && npm test", "test": "uvu test" }, + "dependencies": { + "@types/estree": "^1.0.0" + }, "devDependencies": { - "@types/estree": "0.0.42", - "typescript": "^3.7.5", + "typescript": "^4.9.0", "uvu": "^0.5.1" }, "files": [ diff --git a/src/async.js b/src/async.js index 54276b5..f068c71 100644 --- a/src/async.js +++ b/src/async.js @@ -1,40 +1,56 @@ -// @ts-check import { WalkerBase } from './walker.js'; -/** @typedef { import('estree').BaseNode} BaseNode */ -/** @typedef { import('./walker').WalkerContext} WalkerContext */ - -/** @typedef {( +/** + * @typedef { import('estree').Node} Node + * @typedef { import('./walker.js').WalkerContext} WalkerContext + * @typedef {( * this: WalkerContext, - * node: BaseNode, - * parent: BaseNode, - * key: string, - * index: number - * ) => Promise} AsyncHandler */ + * node: Node, + * parent: Node | null, + * key: string | number | symbol | null | undefined, + * index: number | null | undefined + * ) => Promise} AsyncHandler + */ export class AsyncWalker extends WalkerBase { /** * - * @param {AsyncHandler} enter - * @param {AsyncHandler} leave + * @param {AsyncHandler} [enter] + * @param {AsyncHandler} [leave] */ constructor(enter, leave) { super(); - /** @type {AsyncHandler} */ + /** @type {boolean} */ + this.should_skip = false; + + /** @type {boolean} */ + this.should_remove = false; + + /** @type {Node | null} */ + this.replacement = null; + + /** @type {WalkerContext} */ + this.context = { + skip: () => (this.should_skip = true), + remove: () => (this.should_remove = true), + replace: (node) => (this.replacement = node) + }; + + /** @type {AsyncHandler | undefined} */ this.enter = enter; - /** @type {AsyncHandler} */ + /** @type {AsyncHandler | undefined} */ this.leave = leave; } /** - * - * @param {BaseNode} node - * @param {BaseNode} parent - * @param {string} [prop] - * @param {number} [index] - * @returns {Promise} + * @template {Node} Parent + * @param {Node} node + * @param {Parent | null} parent + * @param {keyof Parent} [prop] + * @param {number | null} [index] + * @returns {Promise} */ async visit(node, parent, prop, index) { if (node) { @@ -68,22 +84,28 @@ export class AsyncWalker extends WalkerBase { if (removed) return null; } - for (const key in node) { + /** @type {keyof Node} */ + let key; + + for (key in node) { + /** @type {unknown} */ const value = node[key]; - if (typeof value !== "object") { - continue; - } else if (Array.isArray(value)) { - for (let i = 0; i < value.length; i += 1) { - if (value[i] !== null && typeof value[i].type === 'string') { - if (!(await this.visit(value[i], node, key, i))) { - // removed - i--; + if (value && typeof value === 'object') { + if (Array.isArray(value)) { + const nodes = /** @type {Array} */ (value); + for (let i = 0; i < nodes.length; i += 1) { + const item = nodes[i]; + if (isNode(item)) { + if (!(await this.visit(item, node, key, i))) { + // removed + i--; + } } } + } else if (isNode(value)) { + await this.visit(value, node, key, null); } - } else if (value !== null && typeof value.type === "string") { - await this.visit(value, node, key, null); } } @@ -116,3 +138,15 @@ export class AsyncWalker extends WalkerBase { return node; } } + +/** + * Ducktype a node. + * + * @param {unknown} value + * @returns {value is Node} + */ +function isNode(value) { + return ( + value !== null && typeof value === 'object' && 'type' in value && typeof value.type === 'string' + ); +} diff --git a/src/index.js b/src/index.js index dcf4c72..933ea4f 100644 --- a/src/index.js +++ b/src/index.js @@ -1,19 +1,19 @@ -// @ts-check import { SyncWalker } from './sync.js'; import { AsyncWalker } from './async.js'; -/** @typedef { import('estree').BaseNode} BaseNode */ -/** @typedef { import('./sync.js').SyncHandler} SyncHandler */ -/** @typedef { import('./async.js').AsyncHandler} AsyncHandler */ +/** + * @typedef {import('estree').Node} Node + * @typedef {import('./sync.js').SyncHandler} SyncHandler + * @typedef {import('./async.js').AsyncHandler} AsyncHandler + */ /** - * - * @param {BaseNode} ast + * @param {Node} ast * @param {{ * enter?: SyncHandler * leave?: SyncHandler * }} walker - * @returns {BaseNode} + * @returns {Node | null} */ export function walk(ast, { enter, leave }) { const instance = new SyncWalker(enter, leave); @@ -21,13 +21,12 @@ export function walk(ast, { enter, leave }) { } /** - * - * @param {BaseNode} ast + * @param {Node} ast * @param {{ * enter?: AsyncHandler * leave?: AsyncHandler * }} walker - * @returns {Promise} + * @returns {Promise} */ export async function asyncWalk(ast, { enter, leave }) { const instance = new AsyncWalker(enter, leave); diff --git a/src/sync.js b/src/sync.js index b3cea40..171fb36 100644 --- a/src/sync.js +++ b/src/sync.js @@ -1,40 +1,56 @@ -// @ts-check import { WalkerBase } from './walker.js'; -/** @typedef { import('estree').BaseNode} BaseNode */ -/** @typedef { import('./walker.js').WalkerContext} WalkerContext */ - -/** @typedef {( +/** + * @typedef { import('estree').Node} Node + * @typedef { import('./walker.js').WalkerContext} WalkerContext + * @typedef {( * this: WalkerContext, - * node: BaseNode, - * parent: BaseNode, - * key: string, - * index: number - * ) => void} SyncHandler */ + * node: Node, + * parent: Node | null, + * key: string | number | symbol | null | undefined, + * index: number | null | undefined + * ) => void} SyncHandler + */ export class SyncWalker extends WalkerBase { /** * - * @param {SyncHandler} enter - * @param {SyncHandler} leave + * @param {SyncHandler} [enter] + * @param {SyncHandler} [leave] */ constructor(enter, leave) { super(); - /** @type {SyncHandler} */ + /** @type {boolean} */ + this.should_skip = false; + + /** @type {boolean} */ + this.should_remove = false; + + /** @type {Node | null} */ + this.replacement = null; + + /** @type {WalkerContext} */ + this.context = { + skip: () => (this.should_skip = true), + remove: () => (this.should_remove = true), + replace: (node) => (this.replacement = node) + }; + + /** @type {SyncHandler | undefined} */ this.enter = enter; - /** @type {SyncHandler} */ + /** @type {SyncHandler | undefined} */ this.leave = leave; } /** - * - * @param {BaseNode} node - * @param {BaseNode} parent - * @param {string} [prop] - * @param {number} [index] - * @returns {BaseNode} + * @template {Node} Parent + * @param {Node} node + * @param {Parent | null} parent + * @param {keyof Parent} [prop] + * @param {number | null} [index] + * @returns {Node | null} */ visit(node, parent, prop, index) { if (node) { @@ -68,22 +84,28 @@ export class SyncWalker extends WalkerBase { if (removed) return null; } - for (const key in node) { + /** @type {keyof Node} */ + let key; + + for (key in node) { + /** @type {unknown} */ const value = node[key]; - if (typeof value !== "object") { - continue; - } else if (Array.isArray(value)) { - for (let i = 0; i < value.length; i += 1) { - if (value[i] !== null && typeof value[i].type === 'string') { - if (!this.visit(value[i], node, key, i)) { - // removed - i--; + if (value && typeof value === 'object') { + if (Array.isArray(value)) { + const nodes = /** @type {Array} */ (value); + for (let i = 0; i < nodes.length; i += 1) { + const item = nodes[i]; + if (isNode(item)) { + if (!this.visit(item, node, key, i)) { + // removed + i--; + } } } + } else if (isNode(value)) { + this.visit(value, node, key, null); } - } else if (value !== null && typeof value.type === "string") { - this.visit(value, node, key, null); } } @@ -116,3 +138,15 @@ export class SyncWalker extends WalkerBase { return node; } } + +/** + * Ducktype a node. + * + * @param {unknown} value + * @returns {value is Node} + */ +function isNode(value) { + return ( + value !== null && typeof value === 'object' && 'type' in value && typeof value.type === 'string' + ); +} diff --git a/src/walker.js b/src/walker.js index 3555d88..6dc6bd7 100644 --- a/src/walker.js +++ b/src/walker.js @@ -1,11 +1,11 @@ -// @ts-check -/** @typedef { import('estree').BaseNode} BaseNode */ - -/** @typedef {{ - skip: () => void; - remove: () => void; - replace: (node: BaseNode) => void; -}} WalkerContext */ +/** + * @typedef { import('estree').Node} Node + * @typedef {{ + * skip: () => void; + * remove: () => void; + * replace: (node: Node) => void; + * }} WalkerContext + */ export class WalkerBase { constructor() { @@ -15,7 +15,7 @@ export class WalkerBase { /** @type {boolean} */ this.should_remove = false; - /** @type {BaseNode | null} */ + /** @type {Node | null} */ this.replacement = null; /** @type {WalkerContext} */ @@ -27,32 +27,32 @@ export class WalkerBase { } /** - * - * @param {any} parent - * @param {string} prop - * @param {number} index - * @param {BaseNode} node + * @template {Node} Parent + * @param {Parent | null | undefined} parent + * @param {keyof Parent | null | undefined} prop + * @param {number | null | undefined} index + * @param {Node} node */ replace(parent, prop, index, node) { - if (parent) { - if (index !== null) { - parent[prop][index] = node; + if (parent && prop) { + if (index != null) { + /** @type {Array} */ (parent[prop])[index] = node; } else { - parent[prop] = node; + /** @type {Node} */ (parent[prop]) = node; } } } /** - * - * @param {any} parent - * @param {string} prop - * @param {number} index + * @template {Node} Parent + * @param {Parent | null | undefined} parent + * @param {keyof Parent | null | undefined} prop + * @param {number | null | undefined} index */ remove(parent, prop, index) { - if (parent) { - if (index !== null) { - parent[prop].splice(index, 1); + if (parent && prop) { + if (index !== null && index !== undefined) { + /** @type {Array} */ (parent[prop]).splice(index, 1); } else { delete parent[prop]; } diff --git a/test/test.mjs b/test/test.mjs index c9ab449..9b29a7d 100644 --- a/test/test.mjs +++ b/test/test.mjs @@ -1,4 +1,3 @@ -// @ts-check import * as uvu from 'uvu'; import * as assert from 'uvu/assert'; import { walk, asyncWalk } from '../src/index.js'; diff --git a/tsconfig.json b/tsconfig.json index 98b9f9f..8158fe1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,8 @@ { "compilerOptions": { - "allowJs": true, + "checkJs": true, + "strict": true, + "module": "node16", "target": "es2017", "declaration": true, "emitDeclarationOnly": true,