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

Eslint: Exempt TS files from jsdoc/require-param #39458

Merged
merged 3 commits into from Mar 17, 2022
Merged

Eslint: Exempt TS files from jsdoc/require-param #39458

merged 3 commits into from Mar 17, 2022

Conversation

mirka
Copy link
Member

@mirka mirka commented Mar 15, 2022

What?

Exempts TypeScript files from the jsdoc/require-param rule.

Why?

TypeScript files generally have their type information in code rather than in JSDoc comments. The jsdoc/require-param rule requires all params to be documented in the JSDoc, which would be redundant in these cases.

For example, jsdoc/require-param would consider this an error, because props is not documented in the comment:

/**
 * This is Foo
 */
export function Foo( props: Props ) { ... }

Testing Instructions

  • Simple doc comments like the above should not trigger eslint errors in TS files.
  • The behavior remains the same for .js files.

@mirka mirka added the [Type] Code Quality Issues or PRs that relate to code quality label Mar 15, 2022
@mirka mirka requested review from gziolo and ciampo March 15, 2022 12:41
@mirka mirka self-assigned this Mar 15, 2022
@github-actions
Copy link

github-actions bot commented Mar 15, 2022

Size Change: +486 B (0%)

Total Size: 1.16 MB

Filename Size Change
build/block-editor/index.min.js 145 kB +95 B (0%)
build/block-editor/style-rtl.css 15 kB +5 B (0%)
build/block-editor/style.css 15 kB +6 B (0%)
build/block-library/index.min.js 168 kB +52 B (0%)
build/components/index.min.js 218 kB +315 B (0%)
build/components/style-rtl.css 15.6 kB +15 B (0%)
build/components/style.css 15.6 kB +12 B (0%)
build/customize-widgets/index.min.js 11.2 kB -18 B (0%)
build/data/index.min.js 8.19 kB +141 B (+2%)
build/edit-navigation/index.min.js 16.1 kB -17 B (0%)
build/edit-post/index.min.js 29.8 kB -17 B (0%)
build/edit-site/index.min.js 43.8 kB -76 B (0%)
build/edit-widgets/index.min.js 16.5 kB -27 B (0%)
ℹ️ View Unchanged
Filename Size
build/a11y/index.min.js 993 B
build/admin-manifest/index.min.js 1.24 kB
build/annotations/index.min.js 2.77 kB
build/api-fetch/index.min.js 2.27 kB
build/autop/index.min.js 2.15 kB
build/blob/index.min.js 487 B
build/block-directory/index.min.js 6.49 kB
build/block-directory/style-rtl.css 1.01 kB
build/block-directory/style.css 1.01 kB
build/block-editor/default-editor-styles-rtl.css 378 B
build/block-editor/default-editor-styles.css 378 B
build/block-library/blocks/archives/editor-rtl.css 61 B
build/block-library/blocks/archives/editor.css 60 B
build/block-library/blocks/archives/style-rtl.css 65 B
build/block-library/blocks/archives/style.css 65 B
build/block-library/blocks/audio/editor-rtl.css 150 B
build/block-library/blocks/audio/editor.css 150 B
build/block-library/blocks/audio/style-rtl.css 111 B
build/block-library/blocks/audio/style.css 111 B
build/block-library/blocks/audio/theme-rtl.css 125 B
build/block-library/blocks/audio/theme.css 125 B
build/block-library/blocks/block/editor-rtl.css 161 B
build/block-library/blocks/block/editor.css 161 B
build/block-library/blocks/button/editor-rtl.css 445 B
build/block-library/blocks/button/editor.css 445 B
build/block-library/blocks/button/style-rtl.css 560 B
build/block-library/blocks/button/style.css 560 B
build/block-library/blocks/buttons/editor-rtl.css 292 B
build/block-library/blocks/buttons/editor.css 292 B
build/block-library/blocks/buttons/style-rtl.css 275 B
build/block-library/blocks/buttons/style.css 275 B
build/block-library/blocks/calendar/style-rtl.css 207 B
build/block-library/blocks/calendar/style.css 207 B
build/block-library/blocks/categories/editor-rtl.css 84 B
build/block-library/blocks/categories/editor.css 83 B
build/block-library/blocks/categories/style-rtl.css 79 B
build/block-library/blocks/categories/style.css 79 B
build/block-library/blocks/code/style-rtl.css 103 B
build/block-library/blocks/code/style.css 103 B
build/block-library/blocks/code/theme-rtl.css 124 B
build/block-library/blocks/code/theme.css 124 B
build/block-library/blocks/columns/editor-rtl.css 108 B
build/block-library/blocks/columns/editor.css 108 B
build/block-library/blocks/columns/style-rtl.css 406 B
build/block-library/blocks/columns/style.css 406 B
build/block-library/blocks/comment-author-avatar/editor-rtl.css 125 B
build/block-library/blocks/comment-author-avatar/editor.css 125 B
build/block-library/blocks/comment-template/style-rtl.css 127 B
build/block-library/blocks/comment-template/style.css 127 B
build/block-library/blocks/comments-pagination-numbers/editor-rtl.css 123 B
build/block-library/blocks/comments-pagination-numbers/editor.css 121 B
build/block-library/blocks/comments-pagination/editor-rtl.css 222 B
build/block-library/blocks/comments-pagination/editor.css 209 B
build/block-library/blocks/comments-pagination/style-rtl.css 235 B
build/block-library/blocks/comments-pagination/style.css 231 B
build/block-library/blocks/comments-query-loop/editor-rtl.css 95 B
build/block-library/blocks/comments-query-loop/editor.css 95 B
build/block-library/blocks/cover/editor-rtl.css 546 B
build/block-library/blocks/cover/editor.css 547 B
build/block-library/blocks/cover/style-rtl.css 1.56 kB
build/block-library/blocks/cover/style.css 1.56 kB
build/block-library/blocks/embed/editor-rtl.css 293 B
build/block-library/blocks/embed/editor.css 293 B
build/block-library/blocks/embed/style-rtl.css 417 B
build/block-library/blocks/embed/style.css 417 B
build/block-library/blocks/embed/theme-rtl.css 124 B
build/block-library/blocks/embed/theme.css 124 B
build/block-library/blocks/file/editor-rtl.css 300 B
build/block-library/blocks/file/editor.css 300 B
build/block-library/blocks/file/style-rtl.css 255 B
build/block-library/blocks/file/style.css 255 B
build/block-library/blocks/file/view.min.js 353 B
build/block-library/blocks/freeform/editor-rtl.css 2.44 kB
build/block-library/blocks/freeform/editor.css 2.44 kB
build/block-library/blocks/gallery/editor-rtl.css 965 B
build/block-library/blocks/gallery/editor.css 967 B
build/block-library/blocks/gallery/style-rtl.css 1.61 kB
build/block-library/blocks/gallery/style.css 1.61 kB
build/block-library/blocks/gallery/theme-rtl.css 122 B
build/block-library/blocks/gallery/theme.css 122 B
build/block-library/blocks/group/editor-rtl.css 159 B
build/block-library/blocks/group/editor.css 159 B
build/block-library/blocks/group/style-rtl.css 57 B
build/block-library/blocks/group/style.css 57 B
build/block-library/blocks/group/theme-rtl.css 78 B
build/block-library/blocks/group/theme.css 78 B
build/block-library/blocks/heading/style-rtl.css 114 B
build/block-library/blocks/heading/style.css 114 B
build/block-library/blocks/html/editor-rtl.css 332 B
build/block-library/blocks/html/editor.css 333 B
build/block-library/blocks/image/editor-rtl.css 731 B
build/block-library/blocks/image/editor.css 730 B
build/block-library/blocks/image/style-rtl.css 529 B
build/block-library/blocks/image/style.css 535 B
build/block-library/blocks/image/theme-rtl.css 124 B
build/block-library/blocks/image/theme.css 124 B
build/block-library/blocks/latest-comments/style-rtl.css 284 B
build/block-library/blocks/latest-comments/style.css 284 B
build/block-library/blocks/latest-posts/editor-rtl.css 199 B
build/block-library/blocks/latest-posts/editor.css 198 B
build/block-library/blocks/latest-posts/style-rtl.css 447 B
build/block-library/blocks/latest-posts/style.css 446 B
build/block-library/blocks/list/style-rtl.css 94 B
build/block-library/blocks/list/style.css 94 B
build/block-library/blocks/media-text/editor-rtl.css 266 B
build/block-library/blocks/media-text/editor.css 263 B
build/block-library/blocks/media-text/style-rtl.css 493 B
build/block-library/blocks/media-text/style.css 490 B
build/block-library/blocks/more/editor-rtl.css 431 B
build/block-library/blocks/more/editor.css 431 B
build/block-library/blocks/navigation-link/editor-rtl.css 708 B
build/block-library/blocks/navigation-link/editor.css 706 B
build/block-library/blocks/navigation-link/style-rtl.css 94 B
build/block-library/blocks/navigation-link/style.css 94 B
build/block-library/blocks/navigation-submenu/editor-rtl.css 299 B
build/block-library/blocks/navigation-submenu/editor.css 299 B
build/block-library/blocks/navigation-submenu/view.min.js 375 B
build/block-library/blocks/navigation/editor-rtl.css 2.03 kB
build/block-library/blocks/navigation/editor.css 2.04 kB
build/block-library/blocks/navigation/style-rtl.css 1.89 kB
build/block-library/blocks/navigation/style.css 1.88 kB
build/block-library/blocks/navigation/view.min.js 2.85 kB
build/block-library/blocks/nextpage/editor-rtl.css 395 B
build/block-library/blocks/nextpage/editor.css 395 B
build/block-library/blocks/page-list/editor-rtl.css 363 B
build/block-library/blocks/page-list/editor.css 363 B
build/block-library/blocks/page-list/style-rtl.css 175 B
build/block-library/blocks/page-list/style.css 175 B
build/block-library/blocks/paragraph/editor-rtl.css 157 B
build/block-library/blocks/paragraph/editor.css 157 B
build/block-library/blocks/paragraph/style-rtl.css 273 B
build/block-library/blocks/paragraph/style.css 273 B
build/block-library/blocks/post-author/style-rtl.css 175 B
build/block-library/blocks/post-author/style.css 176 B
build/block-library/blocks/post-comments-form/style-rtl.css 446 B
build/block-library/blocks/post-comments-form/style.css 446 B
build/block-library/blocks/post-comments/style-rtl.css 521 B
build/block-library/blocks/post-comments/style.css 521 B
build/block-library/blocks/post-excerpt/editor-rtl.css 73 B
build/block-library/blocks/post-excerpt/editor.css 73 B
build/block-library/blocks/post-excerpt/style-rtl.css 69 B
build/block-library/blocks/post-excerpt/style.css 69 B
build/block-library/blocks/post-featured-image/editor-rtl.css 721 B
build/block-library/blocks/post-featured-image/editor.css 721 B
build/block-library/blocks/post-featured-image/style-rtl.css 153 B
build/block-library/blocks/post-featured-image/style.css 153 B
build/block-library/blocks/post-template/editor-rtl.css 99 B
build/block-library/blocks/post-template/editor.css 98 B
build/block-library/blocks/post-template/style-rtl.css 323 B
build/block-library/blocks/post-template/style.css 323 B
build/block-library/blocks/post-terms/style-rtl.css 73 B
build/block-library/blocks/post-terms/style.css 73 B
build/block-library/blocks/post-title/style-rtl.css 80 B
build/block-library/blocks/post-title/style.css 80 B
build/block-library/blocks/preformatted/style-rtl.css 103 B
build/block-library/blocks/preformatted/style.css 103 B
build/block-library/blocks/pullquote/editor-rtl.css 198 B
build/block-library/blocks/pullquote/editor.css 198 B
build/block-library/blocks/pullquote/style-rtl.css 389 B
build/block-library/blocks/pullquote/style.css 388 B
build/block-library/blocks/pullquote/theme-rtl.css 167 B
build/block-library/blocks/pullquote/theme.css 167 B
build/block-library/blocks/query-pagination-numbers/editor-rtl.css 122 B
build/block-library/blocks/query-pagination-numbers/editor.css 121 B
build/block-library/blocks/query-pagination/editor-rtl.css 221 B
build/block-library/blocks/query-pagination/editor.css 211 B
build/block-library/blocks/query-pagination/style-rtl.css 234 B
build/block-library/blocks/query-pagination/style.css 231 B
build/block-library/blocks/query/editor-rtl.css 131 B
build/block-library/blocks/query/editor.css 132 B
build/block-library/blocks/quote/style-rtl.css 201 B
build/block-library/blocks/quote/style.css 201 B
build/block-library/blocks/quote/theme-rtl.css 223 B
build/block-library/blocks/quote/theme.css 226 B
build/block-library/blocks/read-more/style-rtl.css 132 B
build/block-library/blocks/read-more/style.css 132 B
build/block-library/blocks/rss/editor-rtl.css 202 B
build/block-library/blocks/rss/editor.css 204 B
build/block-library/blocks/rss/style-rtl.css 289 B
build/block-library/blocks/rss/style.css 288 B
build/block-library/blocks/search/editor-rtl.css 165 B
build/block-library/blocks/search/editor.css 165 B
build/block-library/blocks/search/style-rtl.css 397 B
build/block-library/blocks/search/style.css 398 B
build/block-library/blocks/search/theme-rtl.css 64 B
build/block-library/blocks/search/theme.css 64 B
build/block-library/blocks/separator/editor-rtl.css 99 B
build/block-library/blocks/separator/editor.css 99 B
build/block-library/blocks/separator/style-rtl.css 233 B
build/block-library/blocks/separator/style.css 233 B
build/block-library/blocks/separator/theme-rtl.css 172 B
build/block-library/blocks/separator/theme.css 172 B
build/block-library/blocks/shortcode/editor-rtl.css 474 B
build/block-library/blocks/shortcode/editor.css 474 B
build/block-library/blocks/site-logo/editor-rtl.css 744 B
build/block-library/blocks/site-logo/editor.css 744 B
build/block-library/blocks/site-logo/style-rtl.css 181 B
build/block-library/blocks/site-logo/style.css 181 B
build/block-library/blocks/site-tagline/editor-rtl.css 86 B
build/block-library/blocks/site-tagline/editor.css 86 B
build/block-library/blocks/site-title/editor-rtl.css 84 B
build/block-library/blocks/site-title/editor.css 84 B
build/block-library/blocks/social-link/editor-rtl.css 177 B
build/block-library/blocks/social-link/editor.css 177 B
build/block-library/blocks/social-links/editor-rtl.css 674 B
build/block-library/blocks/social-links/editor.css 673 B
build/block-library/blocks/social-links/style-rtl.css 1.37 kB
build/block-library/blocks/social-links/style.css 1.36 kB
build/block-library/blocks/spacer/editor-rtl.css 332 B
build/block-library/blocks/spacer/editor.css 332 B
build/block-library/blocks/spacer/style-rtl.css 48 B
build/block-library/blocks/spacer/style.css 48 B
build/block-library/blocks/table/editor-rtl.css 471 B
build/block-library/blocks/table/editor.css 472 B
build/block-library/blocks/table/style-rtl.css 481 B
build/block-library/blocks/table/style.css 481 B
build/block-library/blocks/table/theme-rtl.css 188 B
build/block-library/blocks/table/theme.css 188 B
build/block-library/blocks/tag-cloud/style-rtl.css 226 B
build/block-library/blocks/tag-cloud/style.css 227 B
build/block-library/blocks/template-part/editor-rtl.css 235 B
build/block-library/blocks/template-part/editor.css 235 B
build/block-library/blocks/template-part/theme-rtl.css 101 B
build/block-library/blocks/template-part/theme.css 101 B
build/block-library/blocks/text-columns/editor-rtl.css 95 B
build/block-library/blocks/text-columns/editor.css 95 B
build/block-library/blocks/text-columns/style-rtl.css 166 B
build/block-library/blocks/text-columns/style.css 166 B
build/block-library/blocks/verse/style-rtl.css 87 B
build/block-library/blocks/verse/style.css 87 B
build/block-library/blocks/video/editor-rtl.css 571 B
build/block-library/blocks/video/editor.css 572 B
build/block-library/blocks/video/style-rtl.css 173 B
build/block-library/blocks/video/style.css 173 B
build/block-library/blocks/video/theme-rtl.css 124 B
build/block-library/blocks/video/theme.css 124 B
build/block-library/common-rtl.css 934 B
build/block-library/common.css 932 B
build/block-library/editor-rtl.css 9.96 kB
build/block-library/editor.css 9.96 kB
build/block-library/reset-rtl.css 474 B
build/block-library/reset.css 474 B
build/block-library/style-rtl.css 11.4 kB
build/block-library/style.css 11.4 kB
build/block-library/theme-rtl.css 665 B
build/block-library/theme.css 670 B
build/block-serialization-default-parser/index.min.js 1.12 kB
build/block-serialization-spec-parser/index.min.js 2.83 kB
build/blocks/index.min.js 46.5 kB
build/compose/index.min.js 11.2 kB
build/core-data/index.min.js 14.1 kB
build/customize-widgets/style-rtl.css 1.39 kB
build/customize-widgets/style.css 1.39 kB
build/data-controls/index.min.js 663 B
build/date/index.min.js 31.9 kB
build/deprecated/index.min.js 518 B
build/dom-ready/index.min.js 336 B
build/dom/index.min.js 4.53 kB
build/edit-navigation/style-rtl.css 4.04 kB
build/edit-navigation/style.css 4.05 kB
build/edit-post/classic-rtl.css 546 B
build/edit-post/classic.css 547 B
build/edit-post/style-rtl.css 7.07 kB
build/edit-post/style.css 7.07 kB
build/edit-site/style-rtl.css 7.44 kB
build/edit-site/style.css 7.42 kB
build/edit-widgets/style-rtl.css 4.39 kB
build/edit-widgets/style.css 4.39 kB
build/editor/index.min.js 38.4 kB
build/editor/style-rtl.css 3.71 kB
build/editor/style.css 3.71 kB
build/element/index.min.js 4.29 kB
build/escape-html/index.min.js 548 B
build/format-library/index.min.js 6.62 kB
build/format-library/style-rtl.css 571 B
build/format-library/style.css 571 B
build/hooks/index.min.js 1.66 kB
build/html-entities/index.min.js 454 B
build/i18n/index.min.js 3.79 kB
build/is-shallow-equal/index.min.js 535 B
build/keyboard-shortcuts/index.min.js 1.83 kB
build/keycodes/index.min.js 1.41 kB
build/list-reusable-blocks/index.min.js 1.75 kB
build/list-reusable-blocks/style-rtl.css 838 B
build/list-reusable-blocks/style.css 838 B
build/media-utils/index.min.js 2.94 kB
build/notices/index.min.js 957 B
build/nux/index.min.js 2.12 kB
build/nux/style-rtl.css 751 B
build/nux/style.css 749 B
build/plugins/index.min.js 1.98 kB
build/preferences/index.min.js 1.2 kB
build/primitives/index.min.js 949 B
build/priority-queue/index.min.js 611 B
build/react-i18n/index.min.js 704 B
build/react-refresh-entry/index.min.js 8.44 kB
build/react-refresh-runtime/index.min.js 7.31 kB
build/redux-routine/index.min.js 2.69 kB
build/reusable-blocks/index.min.js 2.24 kB
build/reusable-blocks/style-rtl.css 256 B
build/reusable-blocks/style.css 256 B
build/rich-text/index.min.js 11.1 kB
build/server-side-render/index.min.js 1.61 kB
build/shortcode/index.min.js 1.52 kB
build/token-list/index.min.js 668 B
build/url/index.min.js 1.99 kB
build/viewport/index.min.js 1.08 kB
build/warning/index.min.js 280 B
build/widgets/index.min.js 7.21 kB
build/widgets/style-rtl.css 1.16 kB
build/widgets/style.css 1.16 kB
build/wordcount/index.min.js 1.07 kB

compressed-size-action

@ciampo
Copy link
Contributor

ciampo commented Mar 15, 2022

Just playing devil's advocate here — Even if the type of a parameter is defined in TypeScript, the param rule may still be useful for adding a description to a prop or return value.

Would removing the jsdoc/require-param rule make our docs worse?

@mirka
Copy link
Member Author

mirka commented Mar 15, 2022

Would removing the jsdoc/require-param rule make our docs worse?

Not really, in the sense that our jsdoc lint rules are progressively applied. This rule only requires that every parameter is merely listed, and even that is only when someone chooses to add a JSDoc for the function in the first place. Something like @param foo is enough to satisfy it — no type or description required.

It may act as a prompt for the person to add a param description, but in TS files I think it's a bad place to prompt it because we should encourage people to put param descriptions inline rather than in the JSDocs for the function.

// Better ✨
type Props = {
  /** My foo */
  foo: boolean;
}

I'm fine with scoping the override to the wp-components package only, since other packages might have different needs.

Copy link
Contributor

@ciampo ciampo left a comment

Choose a reason for hiding this comment

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

Something like @param foo is enough to satisfy it — no type or description required.
...
It may act as a prompt for the person to add a param description, but in TS files I think it's a bad place to prompt it because we should encourage people to put param descriptions inline rather than in the JSDocs for the function.

Good observations! I'd personally be ok with the changes from this PR, although I wonder if it's better to wait for @gziolo 's thoughts too.

I'm fine with scoping the override to the wp-components package only

I don't have a strong option here, it's probably fine to apply them to all TS files.

@gziolo
Copy link
Member

gziolo commented Mar 15, 2022

Just playing devil's advocate here — Even if the type of a parameter is defined in TypeScript, the param rule may still be useful for adding a description to a prop or return value.

Yes, that's one thing. The other aspect is the tooling we currently use that extracts params from JSDoc to include them in the auto-generated documentation. @dmsnell and @adamziel, how do you handle the cases where you keep full JSDoc comments for functions but you omit types as they are already encoded in TypeScript? How does it work with the tool that generates API documentation?

@dmsnell
Copy link
Contributor

dmsnell commented Mar 15, 2022

we should encourage people to put param descriptions inline rather than in the JSDocs for the function.

Just curious since I'm still rather new to the mix of JSDoc and TypeScript, but why should we encourage inline descriptions? In my limited experience I feel like both constructions have presented approximately the same outcome and experience, and personally I don't mind having the listing of params in the docblock, especially if the descriptions read more like sentences with multiple lines.

/**
 * @param name   Name of the given steel to process; this
 *               could be one of several common names or
 *               the AISI-SAE specification code.
 * @param cycles How many annealing rounds through which
 *               to run the steel. If omitted will use the
 *               recommendation for the given steel type.
 */

vs.

const process = (
	/**
         * Name of the given steel to process; this
         * could be one of several common names or
         * the AISI-SAE specification code.
         */
	name: string,

	/**
	 * How many annealing rounds through which
	 * to run the steel. If omitted will use the
	 * recommendation for the given steel type.
	 */
	cycles?: number
) => {
	
}

Not a big deal, but even in that example I found the inline comments a bit more tedious for formatting and changing the formatting when the comment changes.

how do you handle the cases where you keep full JSDoc comments for functions but you omit types as they are already encoded in TypeScript? How does it work with the tool that generates API documentation?

@sarayourfriend and I removed the requirement that JSDoc params list a type. For one, it was redundant with typed parameters; for two, after a quick survey and some consideration, we found that the types in the docs are generally not that helpful. Most are Object or Function and there's no click-through to jump-to-definition for more complicated types that TypeScript will produce.

It's my strong opinion that JSDocs should intentionally not include typing information when TypeScript provides the types because JSDoc is manual and unconstrained while TypeScript is automated and verified.

That being said I find a lot of value in having explanatory descriptions available for function arguments/parameters and so I find some kind of documentation there crucial, especially in a place like Gutenberg where we often have confusingly-named or outright inaccurately/misleadingly-named arguments.

I'm in favor of keeping the requirement that params have documentation. Our tooling won't discover the inline comments but we've discussed deficiencies in our tooling and a desire to eventually move towards more TypeScript-native tooling which parses the code and extracts all this and provides links to type definitions and the like. My opinion boils down to a more general thought about listing: do people want to require documentation for function arguments? If so then leave this rule in unless we can verify the inline comments. If not then take it out; tinting is really really slow and can be quite annoying when it's wrong (because it assumes it's never wrong, even when presented cases such as the motivation for this very PR).

Copy link
Contributor

@sarayourfriend sarayourfriend left a comment

Choose a reason for hiding this comment

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

TSDoc, the TypeScript specific flavor of JSDoc, still requires params to be documented almost exactly the same way as JSDoc, just without the type. You're correct the type information is redundant but it doesn't mean we can change the way we document parameters entirely, especially not if we want to preserve existing developer experience for easy access to docstrings on function and constant declarations. Inline documentation isn't parsed the same way by babel or other AST builders and therefore isn't used by editors the same way for completion suggestions and inline documentation.

Here is a TypeScript playground that illustrates the difference for editors

Here are screenshots of each function in the playground being used to illustrate the difference in experience between the two:

Inline documentation as proposed by this PR

inlinedocs

Standard TSDoc/JSDoc approach

tsdocdocs

If anything, I would suggest enabling the jsdoc linting rule that requires parameter descriptions for typescript files: https://github.com/gajus/eslint-plugin-jsdoc#user-content-eslint-plugin-jsdoc-rules-require-param-description

Additionally, enabling the TSDoc linter could also be a good idea as there are specific TSDoc conventions that can be followed (and could allow for automatic documentation extraction tools like APIExtractor in the future).

@sarayourfriend
Copy link
Contributor

The example given in the PR description is indeed annoying and I don't have a good solution for it other than suggesting that adding @param props - Component props is minimally invasive and doesn't incur a long-term maintenance cost. In fact, I think the JSDoc linting plugin even considers that rule auto-fixable and will automatically add the param tag with the param name (no description obviously) for you if you enable auto-fixing rules.

@dmsnell
Copy link
Contributor

dmsnell commented Mar 15, 2022

Here is a TypeScript playground that illustrates the difference for editors

Confirmed with the same behavior in PhpStorm

@mirka
Copy link
Member Author

mirka commented Mar 16, 2022

Just curious since I'm still rather new to the mix of JSDoc and TypeScript, but why should we encourage inline descriptions?

Good question, I should've noted the reasoning.

One advantage which I think is universally true, is that in an inline JSDoc we can use additional JSDoc tags like @default and @example within the param documentation. For example, in wp-components we heavily use @default to document the default value for our props:

type Props = {
  /**
   * My foo prop.
   * 
   * @default true
   */
  foo: boolean;
}

Another reason, which may or may not be relevant outside of wp-components, is how it interacts with the actual docgen. From our investigation for wp-components, we found that the only viable docgen for our requirements (react-docgen-typescript) does not extract param descriptions unless they are inline. (I guess that makes sense, because otherwise you'd have the problem of which description to prioritize if there were descriptions both in the function-level @param and inline.) This behavior may be different for other docgens, and may be less of a concern if you only need to deal with a docgen that you control the implementation of (@wordpress/docgen).

In any case, in trying out different docgens I realized that there's a substantial difference in the docgen requirements for React components vs. normal functions, so it might be the case that there is no config that's optimal for both contexts.

@mirka
Copy link
Member Author

mirka commented Mar 16, 2022

Here is a TypeScript playground that illustrates the difference for editors

The inline comment needs to be a doccomment (/** … */), not // …:

function inlineDocs(
  /** `p1` is for indicating when there are llamas in town */
  p1: boolean
) {
  return p1
}

@mirka
Copy link
Member Author

mirka commented Mar 16, 2022

Ok, I tested with @wordpress/docgen (e.g. with this file), and can confirm that this docgen does not currently extract inline param docs. Furthermore, it doesn't pick up the param at all unless it's @param tagged in the function-level JSDoc. So we definitely want to keep this lint rule in place for anything that relies on this docgen! 😆

I limited the scope of the override to wp-components only (4deb885), and tsx files only, to exempt any utils files (a3744a8). I still think it makes sense in the wp-components context where we're mostly dealing with React components and have a clear reason to prefer inline param docs. I don't love the idea of having to add meaningless noise to satisfy a lint rule that doesn't offer a concrete benefit in the components case. (It also involves moving the props destructuring to happen inside the function body, to avoid having to add a long list of @param declarations for every prop.)

What do you all think? Still worth keeping in wp-components?

@sarayourfriend
Copy link
Contributor

@mirka oh sorry, I misunderstood the way inline docs work. They actually are better in some ways than the JSDoc style! You get the parameter documentation per-argument as you're passing it rather than only on the full function. Interesting.

I don't know if the problem with @wordpress/docgen could be overcome. It should be possible (even easier) to document parameters from inline comments: https://astexplorer.net/#/gist/bcfb17e9b46ce3a99838b26b56acf899/dbca20f13ef6a1e5d7a44125784348944883cbe5

You wouldn't need to use comment-parser at all for it... I guess unless tags like @defaultValue were being used for parameters or something. But it should be simpler as you're not having to do the most complex part which was associating a given parameter tag with the parameter (mostly complicated due to object/array destructuring).

@dmsnell
Copy link
Contributor

dmsnell commented Mar 16, 2022

(It also involves moving the props destructuring to happen inside the function body, to avoid having to add a long list of @param declarations for every prop.)

Can you share more about this? It sounds like you are trying to avoid this…

/** Does something
 * @param {Object}  props
 * @param {string}  props.name  name of thing
 * @param {number}  props.count times to do thing
 * @param {boolean} props.flamp whether or not to flamp it too
 */
const Component = ( props ) => { … }

Which, if that's the case, it might be a decent opportunity to create a separate type for the props anyway rather than couple them so tightly to the function's type.

/** @typedef Props
 * @property {string}  name   name of thing
 * @property {number}  count  times to do thing
 * @property {boolean} flamp  whether or not to flamp it too
 */

/** Some component
 * @param {Props} props component props
 */
const Component = props => { … }

const OtherComponent = ( /** @type Props */ props ) => { … }

in an inline JSDoc we can use additional JSDoc tags like @default and @example within the param documentation

I'm not aware of how to add examples for per-property values of an object such as with component props, but JSDoc does have a default value syntax we can use if we want.

/** @param {number} [minimum=56] threshold at which process starts */

This also works with all cases where we define types, such as in @property


There's another option that I think may work for you too without giving up what you want and without changing linter rules. It's possible to go ahead and create TypeScript types for a component written in JS and import those types into JSDoc. This has the advantage that we get the flexibility of TypeScript and when we eventually convert over, we won't have to rewrite the JSDoc types as TS types.

// some JS-only package, without a tsconfig
// types.ts
export interface Props {
	/**
	 * Minimum threshold at which process begins
	 *
	 * @example use a negative number to invert the universe
	 * @default 56
	 */
	minimum?: number;
}
// some JS-only package, in same directory as above
// index.js

/** @typedef {import("./types").Props} MeterProps */


/**
 * Meter with minimum-threshold alarm.
 *
 * @param {MeterProps} props
 */
export default ( props ) => {  }

I tested this out and here's what VSCode and PhpStorm are showing me.
Screen Shot 2022-03-16 at 11 29 23 AM
Screen Shot 2022-03-16 at 11 30 01 AM
Screen Shot 2022-03-16 at 11 30 18 AM

@sarayourfriend
Copy link
Contributor

@dmsnell That's already how things are done in the components package (at least for newer components or older ones that have had type-checking added since the pattern was established by @ciampo and myself last year), specifically using a Props type that is annotated per-property.

I think the idea is that annotating the @param props - Component props feels redundant and the jsdoc linter doesn't allow you to add an annotation on the function without also annotating the params. At least that's my reading of what @mirka is describing the problem to be.

@mirka
Copy link
Member Author

mirka commented Mar 17, 2022

@dmsnell

/** @param {number} [minimum=56] threshold at which process starts */

Ah this is nice, thanks! I don't think I knew that syntax.

Unfortunately react-docgen-typescript disregards JSDoc @ tags at the function-level, so we don't have much leeway in that regard. I was surprised at how different docgen behavior/limitations could be. Some docgens don't support imported types, for example.

(It also involves moving the props destructuring to happen inside the function body, to avoid having to add a long list of @param declarations for every prop.)

Can you share more about this?

Nothing big, this was just about how the linter demands a different number of params depending on where the props are destructured:

/**
 * @param props - Linter needs all the sub-params listed
 * @param props.one
 * @param props.two
 * @param props.three
 */
const Component = ( { one, two, three }: Props ) => {  }

versus

/**
 * @param props - Linter only needs the props obj param
 */
const Component = ( props: Props ) => {
  const { one, two, three } = props;
}

@mirka mirka merged commit bc27909 into trunk Mar 17, 2022
@mirka mirka deleted the eslint-ts-jsdoc branch March 17, 2022 15:45
@github-actions github-actions bot added this to the Gutenberg 12.9 milestone Mar 17, 2022
@dmsnell
Copy link
Contributor

dmsnell commented Mar 17, 2022

Nothing big, this was just about how the linter demands a different number of params depending on where the props are destructured:

Right, which means I think we were talking about the same thing.

Strangely this only impacts TS files inline attribute types. If we were to take your example and add the type signature to props the linter doesn't complain. As much as it would be preferable to have the actual TypeScript inline type, this could potentially be a reasonable compromise in the meantime.

/**
 * @param {Props} props - Linter only needs the props obj param
 */
const Component = ( { one, two, three } ) => {  }

Seems like more mess from the combating opinions of tools that were all overly-simplistic to begin with.

I was surprised at how different docgen behavior/limitations could be. Some docgens don't support imported types, for example.

Yes exactly, and this is my experience with docgen and JS linting tools in general. Again, for one reason or another, they simultaneously ignore important and valid uses of the language to fit the limited model they understand while imposing harsh restrictions on those other uses (instead of being more lenient).

I guess we use react-docgen-typescript for our component playground? I'm still quite leery of piling up the ad-hoc nature of our build tools to accommodate existing contradictions in those tools already. This is surely going to hit the mirror soon where someone wants to remove restrictions from react-docgen-typescript because they conflict with another tool in another spot of the project.

Right now we made the change that the linting rule isn't important for all modules in the components tree, whether or not it's just React props or in any way related to React. That seems like a fairly broad quality escape-hatch for a small convenience. How important are those param descriptions? I don't know. How inconvenient overall is writing them out for the props? I don't know. This just feels neither here nor there, somewhere in between.

It would seem better if we could somehow limit this exemption to React component props, but I don't think we have a way to do that or ascertain what a given argument is.

@mirka
Copy link
Member Author

mirka commented Mar 17, 2022

I guess we use react-docgen-typescript for our component playground?

Correct. We're basically trying to leverage TS data to build out interactive documentation for our component library.

It would seem better if we could somehow limit this exemption to React component props

I'm not too concerned about this at the moment for wp-components, because any non-trivial utility functions tend to be extracted out to .ts files. We can reasonably assume that functions inside .tsx files are React components.

But I do share your concern, and @ciampo and I will be on high alert for any ill effects that we see from this change. The good thing is that it's largely autofixable if we choose to revert.

Personally I feel that human code reviewers tend to be better safeguards for insufficient/bad docs, and enforcing 100% coverage can sometimes even deter people from adding partial yet sufficient docs, or instead add meaningless noise (@param fontSize - The font size). I don't think I'm the only one that sometimes opts for a // … comment instead of /** … */ to document a function because the linter forces me to document all params, and I the human know that that is overkill. That's a clear lose for Intellisense users.

The bulk of the benefit that require-param provides (i.e. nudge someone that forgot to document a type for a new param they added) is already covered in TS files, so I actually think we have very little upside for some rather tangible downsides, React or otherwise.

@dmsnell
Copy link
Contributor

dmsnell commented Mar 17, 2022

any non-trivial utility functions tend to be extracted out to .ts files. We can reasonably assume that functions inside .tsx files are React components.

A cursory glance turned up 32 functions in .tsx files inside of the components directory and I think about half of those don't have any existing JSDoc comments, meaning they are exempted (because our linter is happy to leave you entirely unaware; it mostly cares that if you help a little you must help 100%)

I don't think I'm the only one that sometimes opts for a // … comment instead of /** … */ to document a function because the linter forces me to document all params

this is really concerning 😆 and I think it's reason enough to take this opportunity to review the tooling entirely. we made the decision recently to stop requiring the types on those parameters project-wide so that people could more naturally slide into TypeScript.

also it seems like the problem is again the tools are defective. I'm puzzled also that we added a second docgen library when we already consume one (not that the one in use by our custom docs-gen is better, it isn't). this just makes knowing the rules hard for everyone, particularly because those tools enforce contradictory styles.

React components aren't the only files butting up against this small inconvenience either. I see it every day in all sorts of files I work in. Are React components really that different?

@mirka
Copy link
Member Author

mirka commented Mar 18, 2022

I'm puzzled also that we added a second docgen library when we already consume one

The chain of dependency that inevitably leads to react-docgen-typescript is quite simple:

  1. We want interactive and largely auto-generated component documentation/playgrounds. Unless we roll our own, Storybook is the best choice for this.
  2. Storybook currently supports two TypeScript-ready docgens, one of which is a non-starter due to lack of support for imported types.

Given that we don't have the appetite to build our own Storybook-equivalent from scratch, it makes sense to configure ourselves in a way that plays nice with react-docgen-typescript, as long as it doesn't lock us into any non-standard practices or degrade code quality.

Other wp packages have different documentation needs, so there might not be a one-size-fits-all. In an ideal scenario we'd have lint rules that look at JSDoc and inline types as a whole. Defective tooling, indeed.

React components aren't the only files butting up against this small inconvenience either.

Here's a concrete example of the kind of mess that can happen if we try to "work around" require-param, especially in the react-docgen-typescript case.

/**
 * `BaseControl` is a component used to generate labels and help text for components handling user inputs.
 *
 * @param {WrongProps} props
 */
export const BaseControl: FunctionComponent< BaseControlProps > & {
	VisualLabel: typeof VisualLabel;
} = ( { one, two, three } ) => {}
  • The param type in JSDoc and the inline type is conflicting, but there are no errors thrown. (JSDoc param type is silently ignored)
  • I can't just remove the inline type because the docgen needs it.
  • The IDE continues to prompt me that "JSDoc types may be moved to TypeScript types."

I guess in React you hit this problem a lot more frequently because of the prevalence of object destructuring.

@dmsnell
Copy link
Contributor

dmsnell commented Mar 18, 2022

For the record, I agree with your frustrations here and am not trying to argue that.

try to "work around"

This is where I think we're not on the same page. I'm not trying to say you should add all these things; I'm saying I think we can do better on this in a way that benefits everyone, or disables a faulty tool.

Looking at the documentation for eslint-plugin-jsdoc it looks like our use-case is already supported, and through parameters designed to specifically target these kinds of instances instead of implicitly disabling the whole linter for a specific part of the project.

  • checkTypesPattern lets us define something like Props as an acceptable type for the linter to ignore destructured parameters. This probably isn't the bets way to approach the problem but we could say, for all types starting with a capital letter, don't require @param tags for all the properties.
  • checkDestructured I believe is the rule in question here. it defaults true but we could potentially disable it globally. again, destructuring props is a common pattern. A quick search turned up almost three hundred functions starting with a lower-case name (probably not React) that follow this same pattern.
  • contexts lets us limit a rule or rule change to a certain code construct, such as TSFunction though it's not available on check-param-names or require-param-name

I played around with some variations of these and it looks promising. The worst part about the config options is that the ones we want don't exist for check-param-names. Someone opened an issue with eslint for this very issue.

Maybe we could discuss relaxing the requirement that destructured properties all get a @param tag globally?

jostnes pushed a commit to jostnes/gutenberg that referenced this pull request Mar 23, 2022
* Eslint: Exempt TS files from `jsdoc/require-param`

* Scope to wp-components files only

* Scope to tsx files only
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Type] Code Quality Issues or PRs that relate to code quality
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants