-
Notifications
You must be signed in to change notification settings - Fork 38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
types: add export to package.json, test with strict mode and node16 resolution #34
Changes from all commits
7cc986c
92dfef3
a964433
52e3839
a9dcf7f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<void>} AsyncHandler */ | ||
* node: Node, | ||
* parent: Node | null, | ||
* key: string | number | symbol | null | undefined, | ||
* index: number | null | undefined | ||
* ) => Promise<void>} 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} */ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you assign |
||
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<BaseNode>} | ||
* @template {Node} Parent | ||
* @param {Node} node | ||
* @param {Parent | null} parent | ||
* @param {keyof Parent} [prop] | ||
* @param {number | null} [index] | ||
* @returns {Promise<Node | null>} | ||
*/ | ||
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<unknown>} */ (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' | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These shouldn't need to be here, they are defined by the
super()
callingWalkerBase
.But TypeScript + JSDoc seems to be unable to infer these from the
extends WalkerBase
on the class. 🤔/cc @wooorm @remcohaszing have you ever run into this before? Do you know of any work arounds?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apparently the culprit are these lines in
visit
:It appears TypeScript is having problems recursively inferring these types from themselves. As a workaround, you can type-cast the values that are later assigned back. Note that this occurs twice!
This is definitely a bug in TypeScript. Here’s minimal reproduction. See what happens if you comment the line
this.name = name
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe you need to type fields? Such as like this: https://github.com/vfile/vfile/blob/03efac7cc3a67fe33bb7a0f1de29e17be322549e/lib/index.js#L122-L126
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it’s currently done that way, but it doesn’t make a difference. It’s a bug with subclasses. Also it only happens in JS. If the exact same code in the minimal reproduction is added in a
.ts
file, TypeScript can infer the types perfectly fine.