Skip to content
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

Improve JSDoc comments #1115

Merged

Conversation

andrewiggins
Copy link
Member

@andrewiggins andrewiggins commented May 21, 2018

Update Preact's JSDoc comments to better reflect the current code and improve editor experience (if using editor with TypeScript support/extension). I make use of @typedefs to define a couple of key complex types used by Preact. Each complex type is described below.

Complex Types

Options: Documents all the public options a consumer of Preact can define

VNode: Documents the properties of a Preact Virtual DOM Node

PreactElement: Documents all of the properties Preact adds to the DOM elements it creates

What about Component?

I haven't exhaustively documented Component in this PR because that is a much more complex type to document in JSDoc and I'm considering doing it in its own PR to discuss different ways of how to accomplish it. I've started experimenting with how to do this in my update-jsdoc-component-doc branch if you are curious.

Other Notes

These JSDoc comments rely on a new feature in Typescript 2.9, "allow import(...)-ing types at any location". I use this feature to import @typedefs across files. The next version of VSCode should include TS 2.9 by default so these comments will work by default in that version. If you'd like to check out the TS 2.9 experience now, check out this branch and set the VSCode workspace setting typescript.tsdk to node_modules\\typescript\\lib.

This PR does not eliminate all errors that appear in the editor. In order to do that Preact would likely need to embrace full TypeScript or Flow syntaxes to utilize the entirety of features those languages offer. It might be possible to remove all errors using only JSDoc syntax however I'd like to explore that possibility in a separate PR as it might require an additional significant amount of changes.

I've rewritten many JSDoc comments to the default style that VSCode uses when you add new JSDoc comments.

Fixes #1106

@coveralls
Copy link

coveralls commented May 21, 2018

Coverage Status

Coverage remained the same at 100.0% when pulling 6869b75 on andrewiggins:update-jsdoc-preactelement into 2364da5 on developit:master.

@andrewiggins andrewiggins changed the title Update JSDoc comments Improve JSDoc comments May 21, 2018
Copy link
Member

@marvinhagemeister marvinhagemeister left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's awesome, thanks for your PR 🎉 Admittedly most of my comments are just nitpicking. There are few instances where we need to decide if we should stick with jsdoc and only use TS definitions when necessary or if we should go full on in with TS only.

The best thing about this PR is that vscode can follow the types correctly. I'm really excited about this PR 👍


/**
* A DOM element that has been extended with Preact properties
* @typedef {Element & ElementCSSInlineStyle & PreactElementExtensions} PreactElement
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line here makes me really happy 👍👍

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TS love 😄

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:)

src/component.js Outdated
* @param {object} props Props (eg: JSX attributes) received from parent element/component
* @param {object} state The component's current state
* @param {object} context Context object (if a parent component has provided context)
* @returns {import('./vnode').VNode | void}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At least according to our TypeScript definitions we don't support undefined as a return type here. Have to check which one is more correct.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lib should support returning any value from render, including undefined. IIRC undefined is treated identically to null.

src/dom/index.js Outdated

/**
* A mapping of event types to event listeners
* @typedef {{ [eventType: string]: EventListener}} EventListenerMap
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm unsure if we should go full on with TS here or use plain jsdoc `Object.<string, EventListener>.

@andrewiggins Are there any difference that you are aware why TS made the change here? Or do they just allow writing arbitrary TS declarations inside the brackets with TS 2.9?

Copy link
Member Author

@andrewiggins andrewiggins May 21, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TS parses JSDoc types using the full TS types parser so I believe we can use any TS type syntax here.

They also support JSDoc syntax Object.<string, EventListener> (search for Index signatures to find it), as well as *.

So it is entirely up to us on what syntax to use. Full disclosure, I write TypeScript for my full time job, hence my obvious bias, but I'm happy rewrite these comments if people prefer the JSDoc syntax.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm ambivalent here. We might find the full-on TS syntax a bit heavy, which could hurt contribution. But, it likely provides better docs (which I think is our end goal here).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like we are learning towards JSDoc syntax so I've updated the types to use JSDoc type syntax where possible (though I could use an extra set of eyes to make sure I didn't miss anything).

3 places where I think we should (or have to in some cases) use TS syntax:

  1. (should) Use TS function syntax (e.g. (arg: string) => string) in some places so we can specify the parameter types
  2. (must) Use TS type operations (namely & and |) because I don't know of a JSDoc equivalent
  3. (must) Use TS 2.9 import type syntax (e.g. import('./vnode').VNode) because I don't know of a JSDoc equivalent

src/dom/index.js Outdated
* If `value` is `null`, the attribute/handler will be removed.
* @param {PreactElement} node An element to mutate
* @param {string} name The name/key to set, such as an event or attribute name
* @param {any} old The last value that was set for this name/node pair
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we just need to cast a quick vote if we'll use TS syntax in general or jsdoc. I'm fine with either. For the latter the any type is *.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depends on what we're using the comments for. If we're generating docs from them and * doesn't give the necessary output, we kinda have to go with any. If either work, my preference would be slightly in favor of the JSDoc-compatible types.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per my reply on the other comment, I've updated this to *. TS correctly parses and understands the *.

src/dom/index.js Outdated
* IE & FF throw for certain property-value combinations.
* @param {Node} node The node to set a property on
* @param {string} name The name of the property to set
* @param {any} value The value to set
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to *

src/util.js Outdated
* Copy all properties from `props` onto `obj`.
* @param {object} obj Object onto which properties should be copied.
* @param {object} props Object from which to copy properties.
* @returns {object} obj
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@returns doesn't have a paramenter name, see http://usejsdoc.org/tags-returns.html

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

/**
* Retains a pool of Components for re-use, keyed on component name.
* Note: since component names are not unique or even necessarily available, these are primarily a form of sharding.
* @type {Record<string, Component[]>}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Record<string, Component[]> vs Object.<string, Component[]>. Same as above, I think we just need to settle on a syntax and be consistent with it :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to Object.<string, Component[]>

* Set a component's `props` and possibly re-render the component
* @param {import('../component').Component} component The Component to set props on
* @param {object} props The new props
* @param {number} opts Render options - specifies how to re-render the component
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes we wonder if we should rename opts to something like render mode or something similar.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, agreed. Renamed to renderMode.

src/vnode.js Outdated
* @typedef VNode
* @property {string | Function} nodeName The string of the DOM node to create or Component constructor to render
* @property {Array<VNode | string>} children The children of node
* @property {string | number | undefined} key The key used to identify this VNode in a list
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use either undefined or void throughout all our docs

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically the types mean two different things when used as function return types in the TS type-checker (at least for now).

For example (TS playground),

function (): void {}

The above code uses void, instructing TypeScript that the the function does not need to have a return statement.

However, in this next example,

function (): undefined { return undefined; }

instructs TypeScript that all code paths in the function should lead to a return statement that returns undefined.

So in summary, I suggest use undefined, unless you want to specify that a function doesn't have to have a return statement, then use void. For Preact, the default implementation is just render() {}, so void is the right type to use there. Everywhere else should use undefined.

/**
* Render a Component, triggering necessary lifecycle events and taking High-Order Components into account.
* @param {import('../component').Component} component The component to render
* @param {number} [opts] A number indicating the kind of render to execute (e.g. sync, async, force)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we should rename opts to better reflect its meaning

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to renderMode

@marvinhagemeister
Copy link
Member

@andrewiggins Thanks for addressing the comments. Hope you don't mind that I was rather nitpicky about the types. Only thing left is wether we use jsdoc or full on TS types syntax. I pinged everyone on slack and we should have an answer soon 👍

* @param {VNode} vnode The virtual DOM element to clone
* @param {Object} props Attributes/props to add when cloning
* @param {VNode} rest Any additional arguments will be used as replacement children.
* @param {import('./vnode').VNode} vnode The virtual DOM element to clone
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be easier to kick this import to the top of the file? It'll get stripped out anyway.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh true! I'll do that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, Had to add VNode to the eslint no-unused-var exception

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like we should be consistent with this where possible. The latest changes have a top level import here and all other places use import inside jsdoc.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although I think the top level imports are easier to understand for new contributors, I'm a bit worried about reducing usefulness of no-unused-var-exception. After thinking a bit more about it I think I'll actually prefer the inline imports. What are your thoughts about this? @developit @andrewiggins

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, that's fair. Consistency would be good. I don't have a strong preference for either pattern but since Preact is not TypeScript, I like the clean separation of type-related "code" (i.e. JSDoc) and real code. Putting the unused import at the top of the file may seem like a bug to new contributors since no real code uses it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marvinhagemeister I've gone ahead and reverted the change back to importing the type inside the JSDoc comment to be consistent with the rest of the changes in this PR. I suggest if we want to use ES6 imports purely for JSDoc comments, we can do that in another PR that applies it consistently across the codebase. Doing that we can evaluate what it does to the codebase and better consider the maintenance tradeoffs, etc.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree 💯

src/component.js Outdated
* @param {object} props Props (eg: JSX attributes) received from parent element/component
* @param {object} state The component's current state
* @param {object} context Context object (if a parent component has provided context)
* @returns {import('./vnode').VNode | void}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lib should support returning any value from render, including undefined. IIRC undefined is treated identically to null.

src/component.js Outdated
*/
this.state = this.state || {};
}


extend(Component.prototype, {

/** Returns a `boolean` indicating if the component should re-render when receiving the given `props` and `state`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way for us to keep documentation for these methods without them being defined on the prototype? Ideally I'd love to have the whole Component API documented as JSDoc annotations here, but only setState(), forceUpdate() and render() actually implemented.

Copy link
Member Author

@andrewiggins andrewiggins May 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I agree - documenting all the lifecycles here in JSDoc without having to define them on the prototype would be ideal. I'm experimenting with some techniques in a branch in my fork (a commmit with one experiement), but so far they all have some compromises. If I get to a good place with a good approach I'll open a PR.

src/component.js Outdated
* @param {object} state A hash of state properties to update with new values
* @param {function} callback A function to be called once component state is updated
/**
* Update component state by copying properties from `state` to `this.state`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can change this description to be more user-facing:

Update component state and schedule a re-render.

src/component.js Outdated
* Virtual DOM is generally constructed via [JSX](http://jasonformat.com/wtf-is-jsx).
* @param {object} props Props (eg: JSX attributes) received from parent element/component
* @param {object} state The component's current state
* @param {object} context Context object (if a parent component has provided context)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For context, this description became incorrect in Preact 4. We could change it to:

Context object, as returned by the nearest  ancestor's `getChildContext()`

src/constants.js Outdated
@@ -1,13 +1,17 @@
// render modes

/** Do not recursively re-render a component */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC this flag disables re-rendering entirely (s/recursively //)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea that's right, I believe

src/constants.js Outdated
export const NO_RENDER = 0;
/** Recursively re-render a component and it's children */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Synchronously re-render a component and its children"

src/constants.js Outdated
export const SYNC_RENDER = 1;
/** Force a re-render of a component */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Synchronously re-render a component, even if its lifecycle methods attempt to prevent it.

src/dom/index.js Outdated

/**
* A mapping of event types to event listeners
* @typedef {{ [eventType: string]: EventListener}} EventListenerMap
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm ambivalent here. We might find the full-on TS syntax a bit heavy, which could hurt contribution. But, it likely provides better docs (which I think is our end goal here).

src/dom/index.js Outdated
* If `value` is `null`, the attribute/handler will be removed.
* @param {PreactElement} node An element to mutate
* @param {string} name The name/key to set, such as an event or attribute name
* @param {any} old The last value that was set for this name/node pair
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depends on what we're using the comments for. If we're generating docs from them and * doesn't give the necessary output, we kinda have to go with any. If either work, my preference would be slightly in favor of the JSDoc-compatible types.

@andrewiggins
Copy link
Member Author

@marvinhagemeister @developit I believed I've resolved all the comments on this PR. When you have time, let me know what you think. Thanks for taking the time to leave your thoughts.

@marvinhagemeister No worries about the "nitpicky"-ness 😄 I thought your comments were were helpful.

Copy link
Member

@marvinhagemeister marvinhagemeister left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, this is really awesome! Being able to click through the types is something that has been missing from our code for a while. Again solid work, and love the thoughtfulness. Congrats to another successful PR 🎉 👍

@marvinhagemeister marvinhagemeister merged commit 2399c49 into preactjs:master May 30, 2018
@andrewiggins andrewiggins deleted the update-jsdoc-preactelement branch May 30, 2018 14:15
markerdmann pushed a commit to EntityProtocol/preact that referenced this pull request Aug 10, 2018
Update Preact's JSDoc comments to better reflect the current code and improve editor experience (if using editor with TypeScript support/extension). I make use of `@typedef`s to define a couple of key complex types used by Preact. Each complex type is described below.

## Complex Types

**Options**: Documents all the public options a consumer of Preact can define

**VNode**: Documents the properties of a Preact Virtual DOM Node

**PreactElement**: Documents all of the properties Preact adds to the DOM elements it creates

## What about Component?

I haven't exhaustively documented Component in this PR because that is a much more complex type to document in JSDoc and I'm considering doing it in its own PR to discuss different ways of how to accomplish it. I've started experimenting with how to do this in my [update-jsdoc-component-doc branch](https://github.com/andrewiggins/preact/blob/update-jsdoc-component-doc/src/component.js) if you are curious.

## Other Notes

These JSDoc comments rely on a new feature in Typescript 2.9, "[allow `import(...)-ing` types at any location](https://github.com/Microsoft/TypeScript/wiki/What%27s-new-in-TypeScript#allow-import-ing-types-at-any-location)". I use this feature to import `@typedef`s across files. The next version of VSCode should include TS 2.9 by default so these comments will work by default in that version. If you'd like to check out the TS 2.9 experience now, check out this branch and set the VSCode workspace setting `typescript.tsdk` to `node_modules\\typescript\\lib`.

This PR does not eliminate all errors that appear in the editor. In order to do that Preact would likely need to embrace full TypeScript or Flow syntaxes to utilize the entirety of features those languages offer. It might be possible to remove all errors using only JSDoc syntax however I'd like to explore that possibility in a separate PR as it might require an additional significant amount of changes.

I've rewritten many JSDoc comments to the default style that VSCode uses when you add new JSDoc comments.

Fixes preactjs#1106
markerdmann pushed a commit to EntityProtocol/preact that referenced this pull request Aug 11, 2018
Update Preact's JSDoc comments to better reflect the current code and improve editor experience (if using editor with TypeScript support/extension). I make use of `@typedef`s to define a couple of key complex types used by Preact. Each complex type is described below.

## Complex Types

**Options**: Documents all the public options a consumer of Preact can define

**VNode**: Documents the properties of a Preact Virtual DOM Node

**PreactElement**: Documents all of the properties Preact adds to the DOM elements it creates

## What about Component?

I haven't exhaustively documented Component in this PR because that is a much more complex type to document in JSDoc and I'm considering doing it in its own PR to discuss different ways of how to accomplish it. I've started experimenting with how to do this in my [update-jsdoc-component-doc branch](https://github.com/andrewiggins/preact/blob/update-jsdoc-component-doc/src/component.js) if you are curious.

## Other Notes

These JSDoc comments rely on a new feature in Typescript 2.9, "[allow `import(...)-ing` types at any location](https://github.com/Microsoft/TypeScript/wiki/What%27s-new-in-TypeScript#allow-import-ing-types-at-any-location)". I use this feature to import `@typedef`s across files. The next version of VSCode should include TS 2.9 by default so these comments will work by default in that version. If you'd like to check out the TS 2.9 experience now, check out this branch and set the VSCode workspace setting `typescript.tsdk` to `node_modules\\typescript\\lib`.

This PR does not eliminate all errors that appear in the editor. In order to do that Preact would likely need to embrace full TypeScript or Flow syntaxes to utilize the entirety of features those languages offer. It might be possible to remove all errors using only JSDoc syntax however I'd like to explore that possibility in a separate PR as it might require an additional significant amount of changes.

I've rewritten many JSDoc comments to the default style that VSCode uses when you add new JSDoc comments.

Fixes preactjs#1106
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants