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

chore: add typedefs for registerBlockType #18257

Closed
wants to merge 2 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
342 changes: 311 additions & 31 deletions packages/blocks/src/api/registration.js
Expand Up @@ -25,12 +25,313 @@ import { isValidIcon, normalizeIconObject } from './utils';
import { DEPRECATED_ENTRY_KEYS } from './constants';

/**
* Render behavior of a block type icon; one of a Dashicon slug, an element,
* or a component.
* The `edit` function describes the structure of your block in the context of
* the editor. This represents what the editor will render when the block is
* used.
*
* @typedef {(string|WPElement|WPComponent)} WPBlockTypeIconRender
* @callback FnEditBlock
Copy link
Member

Choose a reason for hiding this comment

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

Hm, is it fair to call this a callback? It's a component, which in many cases will be something of a callback (function component), but not always. And while technically a Component class is a function, I'm not sure "callback" can apply to a value which needs to be constructed?

https://reactjs.org/docs/components-and-props.html

I suppose the main purpose here is in providing a type for the props it can expect to receive? Ultimately that seems to go back to the purpose propTypes is meant to serve. I could imagine if we adopted propTypes, we might be able to integrate this into the docs tooling to extract documentation for component props.

Then again, with the direct React is taking, we seem to be reaching a point where there's few reasons anyone should ever create a new component that's not a function component, in which case the documentation as you've proposed here might be the most reasonable approach. And above all, it would be good to have something available to document the expected props when used this way.

Copy link
Author

Choose a reason for hiding this comment

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

I would agree in theory, unfortunately I couldn't get JSDoc to understand using any other syntax for documenting functions and their arguments in a detailed manner. I tried @function and just adding @param tags to the typedefs but the parser was not satisfied AFAICT. Perhaps it's just a limitation we accept, use a generic Function and just explain the parameters and return type in the comment block?

Copy link
Member

@aduth aduth Nov 8, 2019

Choose a reason for hiding this comment

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

I would agree in theory, unfortunately I couldn't get JSDoc to understand using any other syntax for documenting functions and their arguments in a detailed manner.

In a side project, I've had luck with the following:

/**
 * @typedef {function(SLAuth): Promise<SLStream[]>} SLProviderGetStreams
 */

(specifically the {function(SomeArgumentType): SomeReturnType} format)

The TypeScript checking verifies the parameter:

image

...and has Intellisense for the result:

image

Copy link
Contributor

Choose a reason for hiding this comment

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

Anything between the brackets in a typedef is parsed identically to how typescript type definitions are parsed...

So if you have a function that looks like this...

function foo(someString) {
  return Promise.resolve(someString);
}

You could make a typedef like this...

/**
 * @typedef {(someString: string) => Promise<string>} FooFunc
 */

Or you could reference it inline like this...

/**
 * Function that takes foo as a parameter.
 * @prop {(someString: string) => Promise<string>} fooFunc
 */
function takeFooFunc(fooFunc) { /* ... */ }

Copy link
Author

Choose a reason for hiding this comment

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

That's good to know about TS compatibility, thanks! I did get arrow function notation to work with VS Code at least but not seeing a single example anywhere in the JSDoc spec made me nervous about committing to that approach 😅

Copy link
Member

@aduth aduth Nov 8, 2019

Choose a reason for hiding this comment

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

Thanks @dsifford , I learned something new!

@chancestrickland : If it's compatible with (a) TypeScript, (b) eslint-plugin-jsdoc, and (c) our documentation generator†, I think it should be fine. I like in the alternate forms @dsifford presents that it gives an option to name the arguments (in my prior comment, it would show as arg0).

Copy link
Contributor

Choose a reason for hiding this comment

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

@chancestrickland Yep, I guess I should have mentioned that the way I describe is not strictly aligned with the rules of jsdoc proper. But the editor features and code hints and all that good stuff that are parsed from these type definitions are done so using Microsoft's tsdoc standards, which is an extension of jsdoc.

So, I suppose it should also be mentioned that doing it this was would probably not pair well with a documentation generator that uses the vanilla jsdoc parser.

*
* @see https://developer.wordpress.org/resource/dashicons/
* @param {WPBlockEditProps} props
* @return {WPComponent}
*/

/**
* The `save` function defines the way in which the different attributes should
* be combined into the final markup, which is then serialized into
* `post_content`.
*
* @callback FnSaveBlock
*
* @param {WPBlockSaveProps} props
* @return {WPComponent}
*/

/**
* @callback FnSetAttributes
*
* @param {WPBlockAttributes} attributes Attributes to set.
* @return {void}
Copy link
Member

Choose a reason for hiding this comment

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

FWIW, many conventions support omitting @return when the return value is undefined (including TypeScript?), and I think this is the general approach we've taken in the rest of the project.

If a function that is not in externs has no return value, you can omit the @return tag, and the compiler will assume that the function returns undefined.

https://developers.google.com/closure/compiler/docs/js-for-compiler#tag-return

Copy link
Contributor

Choose a reason for hiding this comment

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

Just chiming in here for a bit of added distinction...

As far as typescript is concerned, a function that returns undefined must return either undefined or simply return; for all code paths.

A function that returns void may or may not return undefined somewhere, but does not need to return in all code paths.

*/

/**
* Defined behavior of a block type.
*
* @typedef {Object} WPBlock
*
* @property {WPBlockAttributes} [attributes] Attributes provide the
* structured data needs of a
* block. They can exist in
* different forms when they are
* serialized, but they are
* declared together under a
* common interface.
* @property {WPBlockCategory} category Blocks are grouped into
* categories to help users
* browse and discover them.
* @property {WPBlockDeprecation} [deprecated] Array of deprecation handlers
* for the block.
* @property {string} [description] This is a short description
* for your block, which can be
* translated with our
* translation functions.
* @property {FnEditBlock} [edit] Component rendering an
* element to manipulate the
* attributes of a block in the
* context of an editor.
* @property {WPBlockTypeIconRender} [icon] An icon property should be
* specified to make it easier
* to identify a block. These
* can be any of the WordPress
* Dashicons, or a custom `svg`
* element.
* @property {string[]} [keywords] Sometimes a block could have
* aliases that help users
* discover it while searching.
* For example, an `image` block
* could also want to be
* discovered by `photo`. You
* can do so by providing an
* array of terms (which can be
* translated).
* @property {string[]} [parent] Setting `parent` lets a block
* require that it is only
* available when nested within
* the specified blocks.
* @property {FnSaveBlock} save Component describing
* serialized markup structure
* of a block type.
* @property {WPBlockStyle[]} [styles] Block styles can be used to
* provide alternative styles to
* block. It works by adding a
* class name to the block’s
* wrapper. Using CSS, a theme
* developer can target the
* class name for the style
* variation if it is selected.
* @property {WPBlockSupports} [supports] Optional block extended
* support features.
* @property {string} title This is the display title for
* your block, which can be
* translated with our
* translation functions. The
* block inserter will show this
* name.
* @property {WPBlockTransforms} [transforms] Block transformations.
*/

/**
* Editor block alignment options.
*
* @typedef {('left' | 'center' | 'right' | 'wide' | 'full')} WPBlockAlign
Copy link
Member

Choose a reason for hiding this comment

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

Per consistency with below, and some previous conversations about minimizing line length, I assume we might want to remove the spaces around |.

*/

/**
* Editor block attributes object.
*
* @typedef {Object} WPBlockAttributeOptions
*
* @property {string} attribute Use attribute to extract the
Copy link
Member

Choose a reason for hiding this comment

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

Should we / do we need to document that all of these properties are optional?

For documentation, it could be useful to describe how they're associated, even if by types it might not be possible to enforce.

e.g.

  • A value for selector is optionally supported when source is one of the HTML-based sources
  • A value for attribute is required when source: 'attribute'
  • A value for meta is required when source: 'meta'

* value of an attribute from
* markup.
* @property {string} meta Attributes may be obtained from
* a post’s meta rather than from
* the block’s representation in
* saved post content. For this, an
* attribute is required to specify
* its corresponding meta key under
* the meta key
* @property {string} selector A CSS selector used to parse the
* saved block's HTML. The selector
* specified can be an HTML tag, or
* anything queryable such as a
* `class` or `id` attribute.
* @property {WPBlockAttributeSource} source Attribute sources are used to
* define how the block attribute
* values are extracted from saved
* post content. They provide a
* mechanism to map from the saved
* markup to a JavaScript
* representation of a block.
* @property {WPBlockAttributeType} type The type of the attribute.
*
* @example
* ```js
* {
* url: {
* type: 'string',
* source: 'attribute',
* selector: 'img',
* attribute: 'src',
* }
* }
* // { "url": "https://lorempixel.com/1200/800/" }
* ```
*/

/**
* Editor block category options.
*
* @typedef {Object<string, WPBlockAttributeOptions>} WPBlockAttributes
*/

/**
* Editor block category options.
Copy link
Member

Choose a reason for hiding this comment

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

This description is inaccurate (I believe it's copy-pasted from the prior typedef).

*
* @typedef {('text'|'html'|'query'|'meta'|'number'|'string'|'integer')} WPBlockAttributeSource
Copy link
Member

Choose a reason for hiding this comment

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

I don't think all of these options are accurate?

'number', 'string', and 'integer' are not options for an attribute source, and should be removed.

Conversely, there's a few missing, though they're not any I think we should really be recommending: 'children', 'node', 'property', 'tag'.

*/

/**
* Editor block category options.
*
* @typedef {('null'|'boolean'|'object'|'array'|'number'|'string'|'integer')} WPBlockAttributeType
*/

/**
* Editor block category options.
*
* @typedef {('common'|'formatting'|'layout'|'widgets'|'embed')} WPBlockCategory
Copy link
Member

Choose a reason for hiding this comment

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

Since a plugin or theme can add their own custom options, I think we can't assume it would be part of this limited set.

https://developer.wordpress.org/block-editor/developers/filters/block-filters/#managing-block-categories

*/

/**
* @typedef {Object} WPBlockEditProps
*
* @property {WPBlockAttributes} attributes This property surfaces all the
* available attributes and their
* corresponding values, as
* described by the attributes
* property when the block type was
* registered.
* @property {string} [className] This property returns the class
* name for the wrapper element.
* This is automatically added in
* the `save` method, but not on
* `edit`, as the root element may
* not correspond to what is
* visually the main element of the
* block. You can request it to add
* it to the correct element in your
* function.
* @property {boolean} [isSelected] The `isSelected` property is an
* object that communicates whether
* the block is currently selected.
* @property {FnSetAttributes} setAttributes This function allows the block to
* update individual attributes
* based on user interactions.
*/

/**
*
Copy link
Member

Choose a reason for hiding this comment

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

Should there be a description?

* @typedef {Object} WPBlockSaveProps
*
* @property {WPBlockAttributes} attributes As with edit, the save function also
* receives an object argument
* including attributes which can be
* inserted into the markup.
*/

/**
* Editor block style object.
*
* @typedef {Object} WPBlockStyle
*
* @property {string} name Unique key name for the style definition.
* @property {string} label Label shown to the user in the editor.
* @property {boolean} isDefault Whether or not the style is selected by
* default.
*/

/**
* Editor block deprecation options.
*
* @typedef {Object} WPBlockDeprecation
*
* @property {WPBlockAttributes} attributes The attributes definition of the
* deprecated form of the block.
* @property {Function} [isEligible] A function which, given the
* attributes and inner blocks of the
* parsed block, returns true if the
* deprecation can handle the block
* migration. This is particularly
* useful in cases where a block is
* technically valid even once
* deprecated, and requires updates
* to its attributes or inner blocks.
* @property {Function} [migrate] A function which, given the old
* attributes and inner blocks is
* expected to return either the new
* attributes or a tuple array of
* `[attributes, innerBlocks]`
* compatible with the block.
* @property {FnSaveBlock} save The save implementation of the
* deprecated form of the block.
* @property {WPBlockSupports} support The supports definition of the
* deprecated form of the block.
*/

/**
* Editor block category options.
*
* @typedef {Object} WPBlockSupports
*
*
* @property {boolean | WPBlockAlign[]} [align] This property adds block
* controls which allow to change
* block's alignment. Defaults to
* `false`.
* @property {boolean} [alignWide] This property enables wide
* alignment (depends on `align`).
* Defaults to `true`.
* @property {boolean} [anchor] Anchors let you link directly to
* a specific block on a page. This
* property adds a field to define
* an id for the block and a button
* to copy the direct link.
* Defaults to `false`.
* @property {boolean} [customClassName] This property adds a field to
* define a custom className for
* the block's wrapper. Defaults to
* `true`.
* @property {boolean} [className] This property adds a class with
* the form
* `.wp-block-your-block-name` to
* the root element of your saved
* markup. Defaults to `true`.
* @property {boolean} [html] This property allows a block's
* markup to be edited
* individually. Defaults to
* `true`.
* @property {boolean} [inserter] By default, all blocks will
* appear in the Gutenberg
* inserter. To hide a block so
* that it can only be inserted
* programmatically, set this
* property to `false`.
* @property {boolean} [multiple] A non-multiple block can be
* inserted into each post, one
* time only. Defaults to `true`.
* @property {boolean} [reusable] This property allows non-
* multiple block to be converted
* to a reusable block.. Defaults
* to `true`.
*/

/**
* Block transformations.
*
* @typedef {Object} WPBlockTransforms
*
* @property {Object<string,*>[]} from Transforms from another block type to
* this block type.
* @property {Object<string,*>[]} to Transforms from this block type to
* another block type.
*/

/**
* Value to use to render the icon for a block type in an editor interface,
* either a Dashicon slug, an element, a component, or an object describing
* the icon.
*
* @typedef {(WPBlockTypeIconDescriptor|WPBlockTypeIconRender)} WPBlockTypeIcon
*/

/**
Expand All @@ -50,33 +351,12 @@ import { DEPRECATED_ENTRY_KEYS } from './constants';
*/

/**
* Value to use to render the icon for a block type in an editor interface,
* either a Dashicon slug, an element, a component, or an object describing
* the icon.
*
* @typedef {(WPBlockTypeIconDescriptor|WPBlockTypeIconRender)} WPBlockTypeIcon
*/

/**
* Defined behavior of a block type.
* Render behavior of a block type icon; one of a Dashicon slug, an element,
* or a component.
*
* @typedef {Object} WPBlock
* @typedef {(string|WPElement|WPComponent)} WPBlockTypeIconRender
*
* @property {string} name Block type's namespaced name.
* @property {string} title Human-readable block type label.
* @property {string} category Block type category classification,
* used in search interfaces to arrange
* block types by category.
* @property {WPBlockTypeIcon} [icon] Block type icon.
* @property {string[]} [keywords] Additional keywords to produce block
* type as result in search interfaces.
* @property {Object} [attributes] Block type attributes.
* @property {WPComponent} [save] Optional component describing
* serialized markup structure of a
* block type.
* @property {WPComponent} edit Component rendering an element to
* manipulate the attributes of a block
* in the context of an editor.
* @see https://developer.wordpress.org/resource/dashicons/
*/

/**
Expand Down Expand Up @@ -110,8 +390,8 @@ export function unstable__bootstrapServerSideBlockDefinitions( definitions ) { /
* behavior. Once registered, the block is made available as an option to any
* editor interface where blocks are implemented.
*
* @param {string} name Block name.
* @param {Object} settings Block settings.
* @param {string} name Block name.
* @param {WPBlock} settings Block settings.
*
* @return {?WPBlock} The block, if it has been successfully registered;
* otherwise `undefined`.
Expand Down