diff --git a/docs/designers-developers/developers/data/data-core-blocks.md b/docs/designers-developers/developers/data/data-core-blocks.md index 5c6806fa70703..5468744a155d1 100644 --- a/docs/designers-developers/developers/data/data-core-blocks.md +++ b/docs/designers-developers/developers/data/data-core-blocks.md @@ -186,7 +186,7 @@ _Parameters_ _Returns_ -- `Array`: Whether block type matches search term. +- `boolean`: Whether block type matches search term. diff --git a/package-lock.json b/package-lock.json index 03cdd6b0786b9..89aee64068a48 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2983,18 +2983,64 @@ "integrity": "sha512-eAtOAFZefEnfJiRFQBGw1eYqa5GTLCZ1y86N0XSI/D6EB+E8z6VPV/UL7Gi5UEclFqoQk+6NRqEDsfmDLXn8sg==", "dev": true }, + "@types/jest": { + "version": "24.0.15", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-24.0.15.tgz", + "integrity": "sha512-MU1HIvWUme74stAoc3mgAi+aMlgKOudgEvQDIm1v4RkrDudBh1T+NFp5sftpBAdXdx1J0PbdpJ+M2EsSOi1djA==", + "dev": true, + "requires": { + "@types/jest-diff": "*" + } + }, + "@types/jest-diff": { + "version": "20.0.1", + "resolved": "https://registry.npmjs.org/@types/jest-diff/-/jest-diff-20.0.1.tgz", + "integrity": "sha512-yALhelO3i0hqZwhjtcr6dYyaLoCHbAMshwtj6cGxTvHZAKXHsYGdff6E8EPw3xLKY0ELUTQ69Q1rQiJENnccMA==", + "dev": true + }, + "@types/lodash": { + "version": "4.14.136", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.136.tgz", + "integrity": "sha512-0GJhzBdvsW2RUccNHOBkabI8HZVdOXmXbXhuKlDEd5Vv12P7oAVGfomGp3Ne21o5D/qu1WmthlNKFaoZJJeErA==", + "dev": true + }, "@types/node": { "version": "10.12.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.2.tgz", "integrity": "sha512-53ElVDSnZeFUUFIYzI8WLQ25IhWzb6vbddNp8UHlXQyU0ET2RhV5zg0NfubzU7iNMh5bBXb0htCzfvrSVNgzaQ==", "dev": true }, + "@types/prop-types": { + "version": "15.7.1", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.1.tgz", + "integrity": "sha512-CFzn9idOEpHrgdw8JsoTkaDDyRWk1jrzIV8djzcgpq0y9tG4B4lFT+Nxh52DVpDXV+n4+NPNv7M1Dj5uMp6XFg==", + "dev": true + }, "@types/q": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz", "integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==", "dev": true }, + "@types/react": { + "version": "16.8.22", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.8.22.tgz", + "integrity": "sha512-C3O1yVqk4sUXqWyx0wlys76eQfhrQhiDhDlHBrjER76lR2S2Agiid/KpOU9oCqj1dISStscz7xXz1Cg8+sCQeA==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "csstype": "^2.2.0" + } + }, + "@types/react-dom": { + "version": "16.8.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.8.4.tgz", + "integrity": "sha512-eIRpEW73DCzPIMaNBDP5pPIpK1KXyZwNgfxiVagb5iGiz6da+9A5hslSX6GAQKdO7SayVCS/Fr2kjqprgAvkfA==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/stack-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", @@ -3028,6 +3074,167 @@ "@types/unist": "*" } }, + "@types/wordpress__autop": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@types/wordpress__autop/-/wordpress__autop-2.3.0.tgz", + "integrity": "sha512-eOp82U+mRXMzYm90j8jIeLx2DUxgAe/QWpDYwv/8M+YD2amsNDVG29htTD0Hfx2Z+jrOVp37pKNU1LH+hBtOsg==", + "dev": true + }, + "@types/wordpress__blob": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/wordpress__blob/-/wordpress__blob-2.4.0.tgz", + "integrity": "sha512-l1eyHZv0Mpp01N5XYrga1AwWvldKra8sAIE1hmQWbhN7PJWtJq+6ZtD9ncA3VXvjczavUa9WjVFpU+7cx4li/A==", + "dev": true + }, + "@types/wordpress__block-serialization-default-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@types/wordpress__block-serialization-default-parser/-/wordpress__block-serialization-default-parser-3.2.0.tgz", + "integrity": "sha512-p7V27ry0Eb3Zr8p2gFjieER8WA9rs9BR79lY7WJ9H1Dg/9cvilaYWQlq+5WdlMG2mhSlXUR2VkCplp4Rju12ng==", + "dev": true, + "requires": { + "@types/wordpress__block-serialization-spec-parser": "*" + } + }, + "@types/wordpress__block-serialization-spec-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/wordpress__block-serialization-spec-parser/-/wordpress__block-serialization-spec-parser-3.1.0.tgz", + "integrity": "sha512-Sw/IMjWhbkykYZH1WwhilT+wqcdrTSkx548YzDlCJYYmNU8q55kqxKlp7XGuWbc5kw6pCmJXbOBgPOxfyOHz2w==", + "dev": true + }, + "@types/wordpress__blocks": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/@types/wordpress__blocks/-/wordpress__blocks-6.4.3.tgz", + "integrity": "sha512-JSjBKRn/9Oc/ctIPDS5tctfZ9eOY475SwV9U4NSNJwKbEWsixIfkbeUbiiblcUcLdvEgF/etp0NVp3Q1+vOImQ==", + "dev": true, + "requires": { + "@types/wordpress__components": "*", + "@types/wordpress__data": "*", + "@types/wordpress__element": "*" + } + }, + "@types/wordpress__components": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/wordpress__components/-/wordpress__components-7.4.4.tgz", + "integrity": "sha512-Eo2f5FukPx5Mky4NKVRwrWFfI4qPmJ26qKooGyedXcCXV03v18yPLuCoTmIDqD4iORhQu0lSeWefejsuuClwuA==", + "dev": true, + "requires": { + "@types/wordpress__element": "*", + "@types/wordpress__notices": "*", + "@types/wordpress__rich-text": "*", + "re-resizable": "^4.7.1" + }, + "dependencies": { + "re-resizable": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-4.11.0.tgz", + "integrity": "sha512-dye+7rERqNf/6mDT1iwps+4Gf42420xuZgygF33uX178DxffqcyeuHbBuJ382FIcB5iP6mMZOhfW7kI0uXwb/Q==", + "dev": true + } + } + }, + "@types/wordpress__compose": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@types/wordpress__compose/-/wordpress__compose-3.4.0.tgz", + "integrity": "sha512-Gmi1IYpbBpW/4hzENAns5KNPe0jGtB47WxT++Tn6AM3LuDMxiXKcl5gogy0v4Ptexb87LacWgN5gPkhMg6XOCw==", + "dev": true, + "requires": { + "@types/lodash": "*", + "@types/wordpress__element": "*" + } + }, + "@types/wordpress__data": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/@types/wordpress__data/-/wordpress__data-4.6.2.tgz", + "integrity": "sha512-S9HB+tatVS+7feTDRUR4xnQ3QWKVAlw21wlFXr+BTq1YEAwaY7Lr9megP4Ta8f218s1c7Dw8yh2bS6IGdwb2KQ==", + "dev": true, + "requires": { + "@types/wordpress__element": "*", + "redux": "^4.0.1" + }, + "dependencies": { + "redux": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.4.tgz", + "integrity": "sha512-vKv4WdiJxOWKxK0yRoaK3Y4pxxB0ilzVx6dszU2W8wLxlb2yikRph4iV/ymtdJ6ZxpBLFbyrxklnT5yBbQSl3Q==", + "dev": true, + "requires": { + "loose-envify": "^1.4.0", + "symbol-observable": "^1.2.0" + } + } + } + }, + "@types/wordpress__dom": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@types/wordpress__dom/-/wordpress__dom-2.3.0.tgz", + "integrity": "sha512-ykxV2ysRLIi46I+2vwEAcDtqtIa8fFdLH++Lkb25WpfCMUHh3bzM5LxuPuAbJHDlY+jQaeAvikpo93GHMNCA3w==", + "dev": true + }, + "@types/wordpress__element": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/wordpress__element/-/wordpress__element-2.4.0.tgz", + "integrity": "sha512-pc/T2EVrVqeSDy9kCsLBcIGc/j3EQLkuUKNRERW3LpYIPFTcg7M9vQNw457GwnJP/c50zc43DEcROZCass+Egg==", + "dev": true, + "requires": { + "@types/react": "*", + "@types/react-dom": "*", + "csstype": "^2.2.0" + } + }, + "@types/wordpress__hooks": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/wordpress__hooks/-/wordpress__hooks-2.4.0.tgz", + "integrity": "sha512-SixjDsBvPynmMQxBFX3i4nuWtLifqtml7PhSosoCk5RJy9S7xs/G6vvtcnAuslV3BJTyvxEbAsz4WpKaF0bqMw==", + "dev": true + }, + "@types/wordpress__html-entities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@types/wordpress__html-entities/-/wordpress__html-entities-2.3.0.tgz", + "integrity": "sha512-0rs3aJF8/hDliDRdX+PXBh9zrLHVYu37yAPElbnsoLdaVwvJaPc01EibBHk5t++XkPnX4zzBYz8+sRWPUJLtDg==", + "dev": true + }, + "@types/wordpress__i18n": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@types/wordpress__i18n/-/wordpress__i18n-3.4.0.tgz", + "integrity": "sha512-YD/Zh/rof9gAypXXKpfKHOe8w4+deohqwC04q9dt01AyZwSviOFY8mfpa+0gR4t1eyZiYKI8g/Gv6D2ccmYs9g==", + "dev": true + }, + "@types/wordpress__jest-console": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/wordpress__jest-console/-/wordpress__jest-console-3.0.0.tgz", + "integrity": "sha512-EWbI5b1XR7mafMYPWvgPkPcVkdSnxVDAoWkj60HrBNfJyCTtJJUnblyqVs48OGlagTEQ/EkGrNdqSfrXOLOaBw==", + "dev": true, + "requires": { + "@types/jest": "*" + } + }, + "@types/wordpress__notices": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@types/wordpress__notices/-/wordpress__notices-1.5.0.tgz", + "integrity": "sha512-gH/aY72HoDSweYnU5RExJUdIjrkx6zN09YLVUCwE7HSioXu2cs7SIzyeUFnR0DTxBKqG1Dokf+SrfPrYErru7Q==", + "dev": true, + "requires": { + "@types/wordpress__data": "*", + "@types/wordpress__element": "*" + } + }, + "@types/wordpress__rich-text": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@types/wordpress__rich-text/-/wordpress__rich-text-3.4.2.tgz", + "integrity": "sha512-Ze97h0h24KS2uVbOMXjKMnDZ+j9hMdc4sJVI6BBmkfYp13TmbfjCkFIuK6DzWOOKVLsEOVJ6xrN1tX0XpYB53g==", + "dev": true, + "requires": { + "@types/wordpress__data": "*", + "@types/wordpress__element": "*", + "@types/wordpress__rich-text": "*" + } + }, + "@types/wordpress__shortcode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@types/wordpress__shortcode/-/wordpress__shortcode-2.3.0.tgz", + "integrity": "sha512-JMLAPFnjsGfG/1IhNNn+w3+XhYqF0uJkVH43GKgIUsNX8jBKImAQxxf+xFOpH66XsLeCFnbb9PEuUQ6EhRJ+hQ==", + "dev": true + }, "@types/yargs": { "version": "12.0.12", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-12.0.12.tgz", @@ -5931,7 +6138,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": false, + "resolved": "", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true, "optional": true @@ -7687,6 +7894,12 @@ "integrity": "sha512-+7prCSORpXNeR4/fUP3rL+TzqtiFfhMvTd7uEqMdgPvLPt4+uzFUeufx5RHjGTACCargg/DiEt/moMQmvnfkog==", "dev": true }, + "csstype": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.5.tgz", + "integrity": "sha512-JsTaiksRsel5n7XwqPAfB0l3TFKdpjW/kgAELf9vrb5adGA7UCPLajKK5s3nFrcFm3Rkyp/Qkgl73ENc1UY3cA==", + "dev": true + }, "currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", @@ -10213,7 +10426,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": false, + "resolved": "", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true, "optional": true @@ -21691,6 +21904,12 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "dev": true }, + "typescript": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.3.tgz", + "integrity": "sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==", + "dev": true + }, "ua-parser-js": { "version": "0.7.18", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.18.tgz", diff --git a/package.json b/package.json index dff6817d315a5..27b2eb0affad1 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,21 @@ "@babel/runtime-corejs3": "7.4.4", "@babel/traverse": "7.4.4", "@octokit/rest": "16.26.0", + "@types/jest": "24.0.15", + "@types/lodash": "4.14.136", + "@types/wordpress__autop": "2.3.0", + "@types/wordpress__blob": "2.4.0", + "@types/wordpress__block-serialization-default-parser": "3.2.0", + "@types/wordpress__block-serialization-spec-parser": "3.1.0", + "@types/wordpress__blocks": "6.4.3", + "@types/wordpress__compose": "3.4.0", + "@types/wordpress__dom": "2.3.0", + "@types/wordpress__element": "2.4.0", + "@types/wordpress__hooks": "2.4.0", + "@types/wordpress__html-entities": "2.3.0", + "@types/wordpress__i18n": "3.4.0", + "@types/wordpress__jest-console": "3.0.0", + "@types/wordpress__shortcode": "2.3.0", "@wordpress/babel-plugin-import-jsx-pragma": "file:packages/babel-plugin-import-jsx-pragma", "@wordpress/babel-plugin-makepot": "file:packages/babel-plugin-makepot", "@wordpress/babel-preset-default": "file:packages/babel-preset-default", @@ -136,6 +151,7 @@ "source-map-loader": "0.2.4", "sprintf-js": "1.1.1", "stylelint-config-wordpress": "13.1.0", + "typescript": "3.5.3", "uuid": "3.3.2", "webpack": "4.8.3", "worker-farm": "1.7.0" diff --git a/packages/blocks/README.md b/packages/blocks/README.md index 331be71c3a15f..dde809ffd71d2 100644 --- a/packages/blocks/README.md +++ b/packages/blocks/README.md @@ -349,7 +349,7 @@ Returns an array with the child blocks of a given block. _Parameters_ -- _blockName_ `string`: Name of block (example: “latest-posts”). +- _blockName_ `string`: Name of block (example: 'core/columns'). _Returns_ @@ -464,7 +464,7 @@ Returns a boolean indicating if a block has child blocks or not. _Parameters_ -- _blockName_ `string`: Name of block (example: “latest-posts”). +- _blockName_ `string`: Name of block (example: 'core/latest-posts'). _Returns_ @@ -614,7 +614,7 @@ Registers a new block style variation for the given block. _Parameters_ -- _blockName_ `string`: Name of block (example: “core/latest-posts”). +- _blockName_ `string`: Name of block (example: 'core/paragraph'). - _styleVariation_ `Object`: Object containing `name` which is the class name applied to the block and `label` which identifies the variation to the user. # **registerBlockType** diff --git a/packages/blocks/src/api/categories.js b/packages/blocks/src/api/categories.js index 55945bc973bfc..c05cd416e7887 100644 --- a/packages/blocks/src/api/categories.js +++ b/packages/blocks/src/api/categories.js @@ -3,10 +3,14 @@ */ import { dispatch, select } from '@wordpress/data'; +/** + * @typedef {import('@wordpress/blocks').Category} Category + */ + /** * Returns all the block categories. * - * @return {Object[]} Block categories. + * @return {Category[]} Block categories. */ export function getCategories() { return select( 'core/blocks' ).getCategories(); @@ -15,7 +19,7 @@ export function getCategories() { /** * Sets the block categories. * - * @param {Object[]} categories Block categories. + * @param {Category[]} categories Block categories. */ export function setCategories( categories ) { dispatch( 'core/blocks' ).setCategories( categories ); @@ -24,8 +28,8 @@ export function setCategories( categories ) { /** * Updates a category. * - * @param {string} slug Block category slug. - * @param {Object} category Object containing the category properties that should be updated. + * @param {string} slug Block category slug. + * @param {Partial} category Object containing the category properties that should be updated. */ export function updateCategory( slug, category ) { dispatch( 'core/blocks' ).updateCategory( slug, category ); diff --git a/packages/blocks/src/api/children.js b/packages/blocks/src/api/children.js index 7cf70d2da7815..46ad926a52e3f 100644 --- a/packages/blocks/src/api/children.js +++ b/packages/blocks/src/api/children.js @@ -14,17 +14,19 @@ import { renderToString } from '@wordpress/element'; import * as node from './node'; /** - * A representation of a block's rich text value. - * - * @typedef {WPBlockNode[]} WPBlockChildren + * @typedef {(domNode: Node & ParentNode) => ReactChild[]} HpqMatcher + */ + +/** + * @typedef {import('@wordpress/element').ReactChild} ReactChild */ /** * Given block children, returns a serialize-capable WordPress element. * - * @param {WPBlockChildren} children Block children object to convert. + * @param {ReactChild[]} children Block children object to convert. * - * @return {WPElement} A serialize-capable element. + * @return {ReactChild[]} A serialize-capable element. */ export function getSerializeCapableElement( children ) { // The fact that block children are compatible with the element serializer is @@ -39,9 +41,9 @@ export function getSerializeCapableElement( children ) { /** * Given block children, returns an array of block nodes. * - * @param {WPBlockChildren} children Block children object to convert. + * @param {ReactChild[]} children Block children object to convert. * - * @return {Array} An array of individual block nodes. + * @return {ReactChild[]} An array of individual block nodes. */ function getChildrenArray( children ) { // The fact that block children are compatible with the element serializer @@ -54,9 +56,9 @@ function getChildrenArray( children ) { * Given two or more block nodes, returns a new block node representing a * concatenation of its values. * - * @param {...WPBlockChildren} blockNodes Block nodes to concatenate. + * @param {ReactChild[]} blockNodes Block nodes to concatenate. * - * @return {WPBlockChildren} Concatenated block node. + * @return {ReactChild[]} Concatenated block node. */ export function concat( ...blockNodes ) { const result = []; @@ -70,7 +72,7 @@ export function concat( ...blockNodes ) { ); if ( canConcatToPreviousString ) { - result[ result.length - 1 ] += child; + /** @type {string} */ ( result[ result.length - 1 ] ) += child; } else { result.push( child ); } @@ -84,9 +86,9 @@ export function concat( ...blockNodes ) { * Given an iterable set of DOM nodes, returns equivalent block children. * Ignores any non-element/text nodes included in set. * - * @param {Iterable.} domNodes Iterable set of DOM nodes to convert. + * @param {ArrayLike} domNodes list of DOM nodes to convert. * - * @return {WPBlockChildren} Block children equivalent to DOM nodes. + * @return {ReactChild[]} Block children equivalent to DOM nodes. */ export function fromDOM( domNodes ) { const result = []; @@ -104,7 +106,7 @@ export function fromDOM( domNodes ) { /** * Given a block node, returns its HTML string representation. * - * @param {WPBlockChildren} children Block node(s) to convert to string. + * @param {ReactChild[]} children Block node(s) to convert to string. * * @return {string} String HTML representation of block node. */ @@ -120,10 +122,13 @@ export function toHTML( children ) { * * @param {string} selector DOM selector. * - * @return {Function} hpq matcher. + * @return {HpqMatcher} hpq matcher. */ export function matcher( selector ) { return ( domNode ) => { + /** + * @type {Node & ParentNode | null} + */ let match = domNode; if ( selector ) { diff --git a/packages/blocks/src/api/factory.js b/packages/blocks/src/api/factory.js index 8d659b1c87886..bfe7b27713cbc 100644 --- a/packages/blocks/src/api/factory.js +++ b/packages/blocks/src/api/factory.js @@ -9,7 +9,6 @@ import { findIndex, isObjectLike, filter, - first, flatMap, has, uniq, @@ -29,13 +28,28 @@ import { getBlockType, getBlockTypes, getGroupingBlockName } from './registratio import { normalizeBlockType } from './utils'; /** - * Returns a block object given its type and attributes. + * @typedef {import('@wordpress/blocks').Block>} BlockType + */ + +/** + * @template {Record} T + * @typedef {import('@wordpress/blocks').BlockInstance} BlockInstance + */ + +/** + * @template {Record} T + * @typedef {import('@wordpress/blocks').Transform} Transform + */ + +/** + * Returns a block instance given its type and attributes. * - * @param {string} name Block name. - * @param {Object} attributes Block attributes. - * @param {?Array} innerBlocks Nested blocks. + * @template {Record} T + * @param {string} name Block name. + * @param {Partial} [attributes={}] Block attributes. + * @param {Array>} [innerBlocks=[]] Nested blocks. * - * @return {Object} Block object. + * @return {BlockInstance} Block instance. */ export function createBlock( name, attributes = {}, innerBlocks = [] ) { // Get the type definition associated with a registered block. @@ -63,7 +77,7 @@ export function createBlock( name, attributes = {}, innerBlocks = [] ) { } return result; - }, {} ); + }, /** @type {any} */ ( {} ) ); const clientId = uuid(); @@ -82,11 +96,12 @@ export function createBlock( name, attributes = {}, innerBlocks = [] ) { * Given a block object, returns a copy of the block object, optionally merging * new attributes and/or replacing its inner blocks. * - * @param {Object} block Block instance. - * @param {Object} mergeAttributes Block attributes. - * @param {?Array} newInnerBlocks Nested blocks. + * @template {Record} T + * @param {BlockInstance} block Block instance. + * @param {Partial} [mergeAttributes={}] Block attributes. + * @param {Array>} [newInnerBlocks] Nested blocks. * - * @return {Object} A cloned block. + * @return {BlockInstance} A cloned block. */ export function cloneBlock( block, mergeAttributes = {}, newInnerBlocks ) { const clientId = uuid(); @@ -107,9 +122,9 @@ export function cloneBlock( block, mergeAttributes = {}, newInnerBlocks ) { * Returns a boolean indicating whether a transform is possible based on * various bits of context. * - * @param {Object} transform The transform object to validate. - * @param {string} direction Is this a 'from' or 'to' transform. - * @param {Array} blocks The blocks to transform from. + * @param {Transform} transform The transform object to validate. + * @param {'from'|'to'} direction Is this a 'from' or 'to' transform. + * @param {Array>} blocks The blocks to transform from. * * @return {boolean} Is the transform possible? */ @@ -121,7 +136,7 @@ const isPossibleTransformForSource = ( transform, direction, blocks ) => { // If multiple blocks are selected, only multi block transforms // or wildcard transforms are allowed. const isMultiBlock = blocks.length > 1; - const firstBlockName = first( blocks ).name; + const firstBlockName = blocks[ 0 ].name; const isValidForMultiBlocks = isWildcardBlockTransform( transform ) || ! isMultiBlock || transform.isMultiBlock; if ( ! isValidForMultiBlocks ) { return false; @@ -141,7 +156,7 @@ const isPossibleTransformForSource = ( transform, direction, blocks ) => { // Check if the transform's block name matches the source block (or is a wildcard) // only if this is a transform 'from'. - const sourceBlock = first( blocks ); + const sourceBlock = blocks[ 0 ]; const hasMatchingName = direction !== 'from' || transform.blocks.indexOf( sourceBlock.name ) !== -1 || isWildcardBlockTransform( transform ); if ( ! hasMatchingName ) { return false; @@ -168,9 +183,9 @@ const isPossibleTransformForSource = ( transform, direction, blocks ) => { * Returns block types that the 'blocks' can be transformed into, based on * 'from' transforms on other blocks. * - * @param {Array} blocks The blocks to transform from. + * @param {Array>} blocks The blocks to transform from. * - * @return {Array} Block types that the blocks can be transformed into. + * @return {BlockType[]} Block types that the blocks can be transformed into. */ const getBlockTypesForPossibleFromTransforms = ( blocks ) => { if ( isEmpty( blocks ) ) { @@ -201,16 +216,16 @@ const getBlockTypesForPossibleFromTransforms = ( blocks ) => { * Returns block types that the 'blocks' can be transformed into, based on * the source block's own 'to' transforms. * - * @param {Array} blocks The blocks to transform from. + * @param {Array>} blocks The blocks to transform from. * - * @return {Array} Block types that the source can be transformed into. + * @return {BlockType[]} Block types that the source can be transformed into. */ const getBlockTypesForPossibleToTransforms = ( blocks ) => { if ( isEmpty( blocks ) ) { return []; } - const sourceBlock = first( blocks ); + const sourceBlock = blocks[ 0 ]; const blockType = getBlockType( sourceBlock.name ); const transformsTo = getBlockTransforms( 'to', blockType.name ); @@ -237,7 +252,7 @@ const getBlockTypesForPossibleToTransforms = ( blocks ) => { * and if so whether it is a "wildcard" transform * ie: targets "any" block type * - * @param {Object} t the Block transform object + * @param {Transform} t the Block transform object * * @return {boolean} whether transform is a wildcard transform */ @@ -258,7 +273,7 @@ export const isContainerGroupBlock = ( name ) => name === getGroupingBlockName() * Determines whether the provided Blocks are of the same type * (eg: all `core/paragraph`). * - * @param {Array} blocksArray the Block definitions + * @param {Array>} blocksArray the Block definitions * * @return {boolean} whether or not the given Blocks pass the criteria */ @@ -275,9 +290,9 @@ export const isBlockSelectionOfSameType = ( blocksArray = [] ) => { * Returns an array of block types that the set of blocks received as argument * can be transformed into. * - * @param {Array} blocks Blocks array. + * @param {Array>} blocks Blocks array. * - * @return {Array} Block types that the blocks argument can be transformed to. + * @return {BlockType[]} Block types that the blocks argument can be transformed to. */ export function getPossibleBlockTransformations( blocks ) { if ( isEmpty( blocks ) ) { @@ -293,6 +308,7 @@ export function getPossibleBlockTransformations( blocks ) { ] ); } +// eslint-disable-next-line valid-jsdoc /** * Given an array of transforms, returns the highest-priority transform where * the predicate function returns a truthy value. A higher-priority transform @@ -300,10 +316,11 @@ export function getPossibleBlockTransformations( blocks ) { * null if the transforms set is empty or the predicate function returns a * falsey value for all entries. * - * @param {Object[]} transforms Transforms to search. - * @param {Function} predicate Function returning true on matching transform. + * @template {Transform} T + * @param {T[]} transforms Transforms to search. + * @param {(transform: T) => boolean} predicate Function returning true on matching transform. * - * @return {?Object} Highest-priority transform candidate. + * @return {Transform>|null} Highest-priority transform candidate. */ export function findTransform( transforms, predicate ) { // The hooks library already has built-in mechanisms for managing priority @@ -326,16 +343,17 @@ export function findTransform( transforms, predicate ) { return hooks.applyFilters( 'transform', null ); } +// eslint-disable-next-line valid-jsdoc /** * Returns normal block transforms for a given transform direction, optionally * for a specific block by name, or an empty array if there are no transforms. * If no block name is provided, returns transforms for all blocks. A normal * transform object includes `blockName` as a property. * - * @param {string} direction Transform direction ("to", "from"). - * @param {string|Object} blockTypeOrName Block type or name. + * @param {'to'|'from'} direction Transform direction. + * @param {string|BlockType} [blockTypeOrName] Block type or name. * - * @return {Array} Block transforms for direction. + * @return {Array & { blockName: string }>} Block transforms for direction. */ export function getBlockTransforms( direction, blockTypeOrName ) { // When retrieving transforms for all block types, recurse into self. @@ -363,10 +381,10 @@ export function getBlockTransforms( direction, blockTypeOrName ) { /** * Switch one or more blocks into one or more blocks of the new block type. * - * @param {Array|Object} blocks Blocks array or block object. - * @param {string} name Block name. + * @param {BlockInstance|Array>} blocks Blocks array or block object. + * @param {string} name Block name. * - * @return {?Array} Array of blocks or null. + * @return {Array>|null} Array of blocks or null. */ export function switchToBlockType( blocks, name ) { const blocksArray = castArray( blocks ); diff --git a/packages/blocks/src/api/index.js b/packages/blocks/src/api/index.js index 68e60ed43952b..2697212f02d66 100644 --- a/packages/blocks/src/api/index.js +++ b/packages/blocks/src/api/index.js @@ -1,62 +1,82 @@ export { - createBlock, + getCategories, + setCategories, + updateCategory, +} from './categories'; + +export { + default as children, +} from './children'; + +export { cloneBlock, + createBlock, + findTransform, + getBlockTransforms, getPossibleBlockTransformations, switchToBlockType, - getBlockTransforms, - findTransform, } from './factory'; + +export { + default as node, +} from './node'; + export { - default as parse, getBlockAttributes, + default as parse, parseWithAttributeSchema, } from './parser'; -export { pasteHandler, rawHandler, getPhrasingContentSchema } from './raw-handling'; -export { - default as serialize, - getBlockContent, - getBlockDefaultClassName, - getBlockMenuDefaultClassName, - getSaveElement, - getSaveContent, -} from './serializer'; -export { isValidBlockContent } from './validation'; + export { - getCategories, - setCategories, - updateCategory, -} from './categories'; + getPhrasingContentSchema, + pasteHandler, + rawHandler, +} from './raw-handling'; + export { - registerBlockType, - unregisterBlockType, - setFreeformContentHandlerName, - getFreeformContentHandlerName, - setUnregisteredTypeHandlerName, - getUnregisteredTypeHandlerName, - setDefaultBlockName, - getDefaultBlockName, - setGroupingBlockName, - getGroupingBlockName, + getBlockSupport, getBlockType, getBlockTypes, - getBlockSupport, - hasBlockSupport, - isReusableBlock, getChildBlockNames, + getDefaultBlockName, + getFreeformContentHandlerName, + getGroupingBlockName, + getUnregisteredTypeHandlerName, + hasBlockSupport, hasChildBlocks, hasChildBlocksWithInserterSupport, - unstable__bootstrapServerSideBlockDefinitions, // eslint-disable-line camelcase + isReusableBlock, registerBlockStyle, + registerBlockType, + setDefaultBlockName, + setFreeformContentHandlerName, + setGroupingBlockName, + setUnregisteredTypeHandlerName, unregisterBlockStyle, + unregisterBlockType, + unstable__bootstrapServerSideBlockDefinitions, // eslint-disable-line camelcase } from './registration'; + export { - isUnmodifiedDefaultBlock, - normalizeIconObject, - isValidIcon, -} from './utils'; + getBlockContent, + getBlockDefaultClassName, + getBlockMenuDefaultClassName, + getSaveContent, + getSaveElement, + default as serialize, +} from './serializer'; + export { doBlocksMatchTemplate, synchronizeBlocksWithTemplate, } from './templates'; -export { default as children } from './children'; -export { default as node } from './node'; + +export { + isUnmodifiedDefaultBlock, + isValidIcon, + normalizeIconObject, +} from './utils'; + +export { + isValidBlockContent, +} from './validation'; diff --git a/packages/blocks/src/api/node.js b/packages/blocks/src/api/node.js index 075715f1d84c4..1aa20fec4d7c0 100644 --- a/packages/blocks/src/api/node.js +++ b/packages/blocks/src/api/node.js @@ -1,35 +1,29 @@ +/* global Element, Node */ + /** * Internal dependencies */ import * as children from './children'; /** - * Browser dependencies + * @typedef {(domNode: Node & ParentNode) => ReactChild | null} HpqMatcher */ -const { TEXT_NODE, ELEMENT_NODE } = window.Node; /** - * A representation of a single node within a block's rich text value. If - * representing a text node, the value is simply a string of the node value. - * As representing an element node, it is an object of: - * - * 1. `type` (string): Tag name. - * 2. `props` (object): Attributes and children array of WPBlockNode. - * - * @typedef {string|Object} WPBlockNode + * @typedef {import('@wordpress/element').ReactChild} ReactChild */ /** * Given a single node and a node type (e.g. `'br'`), returns true if the node * corresponds to that type, false otherwise. * - * @param {WPBlockNode} node Block node to test - * @param {string} type Node to type to test against. + * @param {ReactChild} node Block node to test + * @param {string} type Node to type to test against. * * @return {boolean} Whether node is of intended type. */ function isNodeOfType( node, type ) { - return node && node.type === type; + return typeof node === 'object' && node.type === type; } /** @@ -40,9 +34,10 @@ function isNodeOfType( node, type ) { * * @param {NamedNodeMap} nodeMap NamedNodeMap to convert to object. * - * @return {Object} Object equivalent value of NamedNodeMap. + * @return {Record} Object equivalent value of NamedNodeMap. */ export function getNamedNodeMapAsObject( nodeMap ) { + /** @type {Record} */ const result = {}; for ( let i = 0; i < nodeMap.length; i++ ) { const { name, value } = nodeMap[ i ]; @@ -58,19 +53,18 @@ export function getNamedNodeMapAsObject( nodeMap ) { * * @throws {TypeError} If non-element/text node is passed. * - * @param {Node} domNode DOM node to convert. + * @param {Element|Node} domNode DOM node to convert. * - * @return {WPBlockNode} Block node equivalent to DOM node. + * @return {ReactChild} Block node equivalent to DOM node. */ export function fromDOM( domNode ) { - if ( domNode.nodeType === TEXT_NODE ) { - return domNode.nodeValue; + if ( domNode.nodeType === Node.TEXT_NODE ) { + return domNode.nodeValue || ''; } - if ( domNode.nodeType !== ELEMENT_NODE ) { + if ( ! ( domNode instanceof Element ) ) { throw new TypeError( - 'A block node can only be created from a node of type text or ' + - 'element.' + 'A block node can only be created from a node of type text or element.' ); } @@ -80,13 +74,14 @@ export function fromDOM( domNode ) { ...getNamedNodeMapAsObject( domNode.attributes ), children: children.fromDOM( domNode.childNodes ), }, + key: null, }; } /** * Given a block node, returns its HTML string representation. * - * @param {WPBlockNode} node Block node to convert to string. + * @param {ReactChild} node Block node to convert to string. * * @return {string} String HTML representation of block node. */ @@ -100,10 +95,13 @@ export function toHTML( node ) { * * @param {string} selector DOM selector. * - * @return {Function} hpq matcher. + * @return {HpqMatcher} hpq matcher. */ export function matcher( selector ) { return ( domNode ) => { + /** + * @type {Node & ParentNode | null} + */ let match = domNode; if ( selector ) { @@ -111,8 +109,11 @@ export function matcher( selector ) { } try { - return fromDOM( match ); - } catch ( error ) { + if ( match ) { + return fromDOM( match ); + } + return null; + } catch { return null; } }; @@ -130,8 +131,8 @@ export function matcher( selector ) { * @private */ export default { - isNodeOfType, fromDOM, - toHTML, + isNodeOfType, matcher, + toHTML, }; diff --git a/packages/blocks/src/api/parser.js b/packages/blocks/src/api/parser.js index cb3a35d5fffe5..c20158d094049 100644 --- a/packages/blocks/src/api/parser.js +++ b/packages/blocks/src/api/parser.js @@ -2,7 +2,7 @@ * External dependencies */ import { parse as hpqParse } from 'hpq'; -import { flow, castArray, mapValues, omit, stubFalse } from 'lodash'; +import { castArray, flow, mapValues, omit, stubFalse } from 'lodash'; /** * WordPress dependencies @@ -25,18 +25,46 @@ import { getCommentDelimitedContent } from './serializer'; import { attr, html, text, query, node, children, prop } from './matchers'; import { normalizeBlockType } from './utils'; +/** + * @typedef {import('@wordpress/blocks').Block>} BlockType + */ + +/** + * @typedef {import('@wordpress/blocks').BlockAttribute} AttributeSchema + */ + +/** + * @typedef {import('@wordpress/blocks').BlockInstance>} BlockInstance + */ + +/** + * @typedef {import('@wordpress/block-serialization-default-parser').Block} BlockParsed + */ + +/** + * @typedef {(content: string) => BlockInstance[]} BlockInstanceParser + */ + +/** + * @typedef {import('@wordpress/block-serialization-default-parser').parse} BlockParser + */ + +/** + * @typedef {import('@wordpress/blocks').Source} Source + */ + /** * Sources which are guaranteed to return a string value. - * - * @type {Set} + * @type {ReadonlyArray} */ -const STRING_SOURCES = new Set( [ +const STRING_SOURCES = [ 'attribute', 'html', 'text', 'tag', -] ); +]; +// eslint-disable-next-line valid-jsdoc /** * Higher-order hpq matcher which enhances an attribute matcher to return true * or false depending on whether the original matcher returns undefined. This @@ -44,7 +72,7 @@ const STRING_SOURCES = new Set( [ * be technically falsey (empty string), though their mere presence should be * enough to infer as true. * - * @param {Function} matcher Original hpq matcher. + * @param {(...args: any[]) => any} matcher Original hpq matcher. * * @return {Function} Enhanced hpq matcher. */ @@ -71,7 +99,7 @@ export const toBooleanAttributeMatcher = ( matcher ) => flow( [ * * @see http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.25 * - * @param {*} value Value to test. + * @param {any} value Value to test. * @param {string} type Type to test. * * @return {boolean} Whether value is of type. @@ -107,7 +135,7 @@ export function isOfType( value, type ) { * * @see http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.25 * - * @param {*} value Value to test. + * @param {any} value Value to test. * @param {string[]} types Types to test. * * @return {boolean} Whether value is of types. @@ -122,8 +150,8 @@ export function isOfTypes( value, types ) { * * @see https://json-schema.org/latest/json-schema-validation.html#rfc.section.6.1.1 * - * @param {*} value Value to test. - * @param {?(Array|string)} type Block attribute schema type. + * @param {any} value Value to test. + * @param {string|string[]} [type] Block attribute schema type. * * @return {boolean} Whether value is valid. */ @@ -137,8 +165,8 @@ export function isValidByType( value, type ) { * * @see https://json-schema.org/latest/json-schema-validation.html#rfc.section.6.1.2 * - * @param {*} value Value to test. - * @param {?Array} enumSet Block attribute schema enum. + * @param {any} value Value to test. + * @param {any[]} [enumSet] Block attribute schema enum. * * @return {boolean} Whether value is valid. */ @@ -155,7 +183,7 @@ export function isValidByEnum( value, enumSet ) { * attribute schema, since the string ambiguity makes it impossible to know the * correct type of multiple to which to cast. * - * @param {Object} attributeSchema Attribute's schema. + * @param {AttributeSchema} attributeSchema Attribute's schema. * * @return {boolean} Whether attribute schema defines an ambiguous string * source. @@ -163,7 +191,7 @@ export function isValidByEnum( value, enumSet ) { export function isAmbiguousStringSource( attributeSchema ) { const { source, type } = attributeSchema; - const isStringSource = STRING_SOURCES.has( source ); + const isStringSource = STRING_SOURCES.includes( source ); const isSingleType = typeof type === 'string'; return isStringSource && isSingleType; @@ -174,7 +202,7 @@ export function isAmbiguousStringSource( attributeSchema ) { * * @param {Object} sourceConfig Attribute Source object. * - * @return {Function} A hpq Matcher. + * @return {Function|void} A hpq Matcher. */ export function matcherFromSource( sourceConfig ) { switch ( sourceConfig.source ) { @@ -211,10 +239,10 @@ export function matcherFromSource( sourceConfig ) { * Given a block's raw content and an attribute's schema returns the attribute's * value depending on its source. * - * @param {string} innerHTML Block's raw content. - * @param {Object} attributeSchema Attribute's schema. + * @param {string} innerHTML Block's raw content. + * @param {Source} attributeSchema Attribute's schema. * - * @return {*} Attribute value. + * @return {any} Attribute value. */ export function parseWithAttributeSchema( innerHTML, attributeSchema ) { return hpqParse( innerHTML, matcherFromSource( attributeSchema ) ); @@ -225,12 +253,12 @@ export function parseWithAttributeSchema( innerHTML, attributeSchema ) { * commentAttributes returns the attribute value depending on its source * definition of the given attribute key. * - * @param {string} attributeKey Attribute key. - * @param {Object} attributeSchema Attribute's schema. - * @param {string} innerHTML Block's raw content. - * @param {Object} commentAttributes Block's comment attributes. + * @param {string} attributeKey Attribute key. + * @param {Source} attributeSchema Attribute's schema. + * @param {string} innerHTML Block's raw content. + * @param {Record} commentAttributes Block's comment attributes. * - * @return {*} Attribute value. + * @return {any} Attribute value. */ export function getBlockAttribute( attributeKey, attributeSchema, innerHTML, commentAttributes ) { const { type, enum: enumSet } = attributeSchema; @@ -269,11 +297,11 @@ export function getBlockAttribute( attributeKey, attributeSchema, innerHTML, com /** * Returns the block attributes of a registered block node given its type. * - * @param {string|Object} blockTypeOrName Block type or name. - * @param {string} innerHTML Raw block content. - * @param {?Object} attributes Known block attributes (from delimiters). + * @param {string|BlockType} blockTypeOrName Block type or name. + * @param {string} innerHTML Raw block content. + * @param {Record} [attributes] Known block attributes (from delimiters). * - * @return {Object} All block attributes. + * @return {Record} All block attributes. */ export function getBlockAttributes( blockTypeOrName, innerHTML, attributes = {} ) { const blockType = normalizeBlockType( blockTypeOrName ); @@ -290,16 +318,20 @@ export function getBlockAttributes( blockTypeOrName, innerHTML, attributes = {} ); } +// FIXME: Cant' use `BlockInstance` here because it's being mutated in the +// function and because this function assumes (likely by mistake) that +// `BlockInstance` will always contain `originalContent`. This is likely a +// bug. /** * Given a block object, returns a new copy of the block with any applicable * deprecated migrations applied, or the original block if it was both valid * and no eligible migrations exist. * - * @param {WPBlock} block Original block object. + * @param {Object} block Original block object. * @param {Object} parsedAttributes Attributes as parsed from the initial * block markup. * - * @return {WPBlock} Migrated block object. + * @return {Object} Migrated block object. */ export function getMigratedBlock( block, parsedAttributes ) { const blockType = getBlockType( block.name ); @@ -372,9 +404,9 @@ export function getMigratedBlock( block, parsedAttributes ) { /** * Creates a block with fallback to the unknown type handler. * - * @param {Object} blockNode Parsed block node. + * @param {BlockParsed} blockNode Parsed block node. * - * @return {?Object} An initialized block object (if possible). + * @return {BlockInstance|void} An initialized block object (if possible). */ export function createBlockWithFallback( blockNode ) { const { blockName: originalName } = blockNode; @@ -504,9 +536,9 @@ export function createBlockWithFallback( blockNode ) { * @see `@wordpress/block-serialization-default-parser` package * @see `@wordpress/block-serialization-spec-parser` package * - * @param {Object} blockNode A block node as returned by a valid parser. - * @param {?Object} options Serialization options. - * @param {?boolean} options.isCommentDelimited Whether to output HTML comments around blocks. + * @param {BlockParsed} blockNode A block node as returned by a valid parser. + * @param {Object} options Serialization options. + * @param {boolean} [options.isCommentDelimited] Whether to output HTML comments around blocks. * * @return {string} An HTML string representing a block. */ @@ -530,9 +562,9 @@ export function serializeBlockNode( blockNode, options = {} ) { /** * Creates a parse implementation for the post content which returns a list of blocks. * - * @param {Function} parseImplementation Parse implementation. + * @param {BlockParser} parseImplementation Parse implementation. * - * @return {Function} An implementation which parses the post content. + * @return {BlockInstanceParser} An implementation which parses the post content. */ const createParse = ( parseImplementation ) => ( content ) => parseImplementation( content ).reduce( ( memo, blockNode ) => { @@ -541,14 +573,14 @@ const createParse = ( parseImplementation ) => memo.push( block ); } return memo; - }, [] ); + }, /** @type {BlockInstance[]} */( [] ) ); /** * Parses the post content with a PegJS grammar and returns a list of blocks. * * @param {string} content The post content. * - * @return {Array} Block list. + * @return {Array>} Block list. */ export const parseWithGrammar = createParse( defaultParse ); diff --git a/packages/blocks/src/api/raw-handling/blockquote-normaliser.js b/packages/blocks/src/api/raw-handling/blockquote-normaliser.js index f08e89791280b..3cf0350192d43 100644 --- a/packages/blocks/src/api/raw-handling/blockquote-normaliser.js +++ b/packages/blocks/src/api/raw-handling/blockquote-normaliser.js @@ -3,6 +3,10 @@ */ import normaliseBlocks from './normalise-blocks'; +// eslint-disable-next-line valid-jsdoc +/** + * @type {import('./').NodeFilterFunc} + */ export default function( node ) { if ( node.nodeName !== 'BLOCKQUOTE' ) { return; diff --git a/packages/blocks/src/api/raw-handling/comment-remover.js b/packages/blocks/src/api/raw-handling/comment-remover.js index fbfa319ccb831..304febf959099 100644 --- a/packages/blocks/src/api/raw-handling/comment-remover.js +++ b/packages/blocks/src/api/raw-handling/comment-remover.js @@ -1,21 +1,18 @@ +/* global Node */ + /** * WordPress dependencies */ import { remove } from '@wordpress/dom'; -/** - * Browser dependencies - */ -const { COMMENT_NODE } = window.Node; - +// eslint-disable-next-line valid-jsdoc /** * Looks for comments, and removes them. * - * @param {Node} node The node to be processed. - * @return {void} + * @type {import('./').NodeFilterFunc} */ export default function( node ) { - if ( node.nodeType === COMMENT_NODE ) { + if ( node.nodeType === Node.COMMENT_NODE ) { remove( node ); } } diff --git a/packages/blocks/src/api/raw-handling/figure-content-reducer.js b/packages/blocks/src/api/raw-handling/figure-content-reducer.js index 86478176fdb88..713acaf716e43 100644 --- a/packages/blocks/src/api/raw-handling/figure-content-reducer.js +++ b/packages/blocks/src/api/raw-handling/figure-content-reducer.js @@ -14,7 +14,7 @@ import { isPhrasingContent } from './phrasing-content'; * @param {Node} node The node to check. * @param {Object} schema The schema to use. * - * @return {boolean} True if figure content, false if not. + * @return {boolean} `true` if figure content, `false` if not. */ function isFigureContent( node, schema ) { const tag = node.nodeName.toLowerCase(); @@ -34,7 +34,7 @@ function isFigureContent( node, schema ) { * @param {Node} node The node to check. * @param {Object} schema The schema to use. * - * @return {boolean} True if it can, false if not. + * @return {boolean} `true` if it can, `false` if not. */ function canHaveAnchor( node, schema ) { const tag = node.nodeName.toLowerCase(); @@ -42,15 +42,12 @@ function canHaveAnchor( node, schema ) { return has( schema, [ 'figure', 'children', 'a', 'children', tag ] ); } +// eslint-disable-next-line valid-jsdoc /** * This filter takes figure content out of paragraphs, wraps it in a figure * element, and moves any anchors with it if needed. * - * @param {Node} node The node to filter. - * @param {Document} doc The document of the node. - * @param {Object} schema The schema to use. - * - * @return {void} + * @type {import('./').NodeFilterFunc} */ export default function( node, doc, schema ) { if ( ! isFigureContent( node, schema ) ) { diff --git a/packages/blocks/src/api/raw-handling/google-docs-uid-remover.js b/packages/blocks/src/api/raw-handling/google-docs-uid-remover.js index adced320975d7..ac00bd22b3bd4 100644 --- a/packages/blocks/src/api/raw-handling/google-docs-uid-remover.js +++ b/packages/blocks/src/api/raw-handling/google-docs-uid-remover.js @@ -3,6 +3,10 @@ */ import { unwrap } from '@wordpress/dom'; +// eslint-disable-next-line valid-jsdoc +/** + * @type {import('./').NodeFilterFunc} + */ export default function( node ) { if ( ! node.id || node.id.indexOf( 'docs-internal-guid-' ) !== 0 ) { return; diff --git a/packages/blocks/src/api/raw-handling/head-remover.js b/packages/blocks/src/api/raw-handling/head-remover.js index d3c96c9841b8c..a63671aa965c5 100644 --- a/packages/blocks/src/api/raw-handling/head-remover.js +++ b/packages/blocks/src/api/raw-handling/head-remover.js @@ -1,3 +1,7 @@ +// eslint-disable-next-line valid-jsdoc +/** + * @type {import('./').NodeFilterFunc} + */ export default function( node ) { if ( node.nodeName !== 'SCRIPT' && diff --git a/packages/blocks/src/api/raw-handling/iframe-remover.js b/packages/blocks/src/api/raw-handling/iframe-remover.js index 35adb29cdf808..896ed8fb80292 100644 --- a/packages/blocks/src/api/raw-handling/iframe-remover.js +++ b/packages/blocks/src/api/raw-handling/iframe-remover.js @@ -3,12 +3,9 @@ */ import { remove } from '@wordpress/dom'; +// eslint-disable-next-line valid-jsdoc /** - * Removes iframes. - * - * @param {Node} node The node to check. - * - * @return {void} + * @type {import('./').NodeFilterFunc} */ export default function( node ) { if ( node.nodeName === 'IFRAME' ) { diff --git a/packages/blocks/src/api/raw-handling/image-corrector.js b/packages/blocks/src/api/raw-handling/image-corrector.js index 780b0adbfef91..c2d7bdd25a6fa 100644 --- a/packages/blocks/src/api/raw-handling/image-corrector.js +++ b/packages/blocks/src/api/raw-handling/image-corrector.js @@ -1,3 +1,5 @@ +/* global File */ + /** * WordPress dependencies */ @@ -6,8 +8,12 @@ import { createBlobURL } from '@wordpress/blob'; /** * Browser dependencies */ -const { atob, File } = window; +const { atob } = window; +// eslint-disable-next-line valid-jsdoc +/** + * @type {import('./').NodeFilterFunc} + */ export default function( node ) { if ( node.nodeName !== 'IMG' ) { return; diff --git a/packages/blocks/src/api/raw-handling/image-corrector.native.js b/packages/blocks/src/api/raw-handling/image-corrector.native.js index d450ac7441730..2b218e998df62 100644 --- a/packages/blocks/src/api/raw-handling/image-corrector.native.js +++ b/packages/blocks/src/api/raw-handling/image-corrector.native.js @@ -1,11 +1,10 @@ - +// eslint-disable-next-line valid-jsdoc /** - * This method check for copy pasted img elements to see if they don't have suspicious attributes. - * - * @param {Node} node The node to check. + * This method check for copy pasted img elements to see if they don't have + * suspicious attributes. * - * @return {void} -*/ + * @type {import('./').NodeFilterFunc} + */ export default function( node ) { if ( node.nodeName !== 'IMG' ) { return; diff --git a/packages/blocks/src/api/raw-handling/index.js b/packages/blocks/src/api/raw-handling/index.js index 58044375d9dc3..60eb1af7fde3e 100644 --- a/packages/blocks/src/api/raw-handling/index.js +++ b/packages/blocks/src/api/raw-handling/index.js @@ -22,8 +22,24 @@ import { export { getPhrasingContentSchema } from './phrasing-content'; export { pasteHandler } from './paste-handler'; +/** + * @callback NodeFilterFunc A function that can mutate a given `ChildNode`. + * @param {ChildNode} node Node to be mutated. + * @param {Document} doc The document containing the node. + * @param {Object} schema The schema. + * @return {void} Node is mutated in place and should not be returned. + */ + +/** + * @typedef {import('@wordpress/blocks').BlockInstance>} BlockInstance + */ + +/** + * @typedef {import('@wordpress/blocks').TransformRaw} TransformRaw + */ + function getRawTransformations() { - return filter( getBlockTransforms( 'from' ), { type: 'raw' } ) + return /** @type {TransformRaw[]} */ ( filter( getBlockTransforms( 'from' ), { type: 'raw' } ) ) .map( ( transform ) => { return transform.isMatch ? transform : { ...transform, @@ -37,11 +53,11 @@ function getRawTransformations() { * top-level tag. The HTML should be filtered to not have any text between * top-level tags and formatted in a way that blocks can handle the HTML. * - * @param {Object} $1 Named parameters. - * @param {string} $1.html HTML to convert. - * @param {Array} $1.rawTransforms Transforms that can be used. + * @param {Object} params Named parameters. + * @param {string} params.html HTML to convert. + * @param {TransformRaw[]} params.rawTransforms Transforms that can be used. * - * @return {Array} An array of blocks. + * @return {BlockInstance[]} An array of blocks. */ function htmlToBlocks( { html, rawTransforms } ) { const doc = document.implementation.createHTMLDocument( '' ); @@ -81,9 +97,10 @@ function htmlToBlocks( { html, rawTransforms } ) { /** * Converts an HTML string to known blocks. * - * @param {string} $1.HTML The HTML to convert. + * @param {Object} options + * @param {string} options.HTML The HTML to convert. * - * @return {Array} A list of blocks. + * @return {BlockInstance[]} A list of blocks. */ export function rawHandler( { HTML = '' } ) { // If we detect block delimiters, parse entirely as blocks. @@ -106,6 +123,7 @@ export function rawHandler( { HTML = '' } ) { // These filters are essential for some blocks to be able to transform // from raw HTML. These filters move around some content or add // additional tags, they do not remove any content. + /** @type {NodeFilterFunc[]} */ const filters = [ // Needed to adjust invalid lists. listReducer, diff --git a/packages/blocks/src/api/raw-handling/list-reducer.js b/packages/blocks/src/api/raw-handling/list-reducer.js index a5f079dcb2488..82e57a4fc7bfe 100644 --- a/packages/blocks/src/api/raw-handling/list-reducer.js +++ b/packages/blocks/src/api/raw-handling/list-reducer.js @@ -13,6 +13,10 @@ function shallowTextContent( element ) { .join( '' ); } +// eslint-disable-next-line valid-jsdoc +/** + * @type {import('./').NodeFilterFunc} + */ export default function( node ) { if ( ! isList( node ) ) { return; diff --git a/packages/blocks/src/api/raw-handling/markdown-converter.js b/packages/blocks/src/api/raw-handling/markdown-converter.js index ead5d6c8cfd96..03612ea314396 100644 --- a/packages/blocks/src/api/raw-handling/markdown-converter.js +++ b/packages/blocks/src/api/raw-handling/markdown-converter.js @@ -26,7 +26,7 @@ const converter = new showdown.Converter( { function slackMarkdownVariantCorrector( text ) { return text.replace( /((?:^|\n)```)([^\n`]+)(```(?:$|\n))/, - ( match, p1, p2, p3 ) => `${ p1 }\n${ p2 }\n${ p3 }` + ( _, p1, p2, p3 ) => `${ p1 }\n${ p2 }\n${ p3 }` ); } diff --git a/packages/blocks/src/api/raw-handling/ms-list-converter.js b/packages/blocks/src/api/raw-handling/ms-list-converter.js index 0181517493f79..b6d170d39c63a 100644 --- a/packages/blocks/src/api/raw-handling/ms-list-converter.js +++ b/packages/blocks/src/api/raw-handling/ms-list-converter.js @@ -1,12 +1,11 @@ -/** - * Browser dependencies - */ -const { parseInt } = window; - function isList( node ) { return node.nodeName === 'OL' || node.nodeName === 'UL'; } +// eslint-disable-next-line valid-jsdoc +/** + * @type {import('./').NodeFilterFunc} + */ export default function( node, doc ) { if ( node.nodeName !== 'P' ) { return; diff --git a/packages/blocks/src/api/raw-handling/normalise-blocks.js b/packages/blocks/src/api/raw-handling/normalise-blocks.js index 52ca57c43e047..366378e04e9fc 100644 --- a/packages/blocks/src/api/raw-handling/normalise-blocks.js +++ b/packages/blocks/src/api/raw-handling/normalise-blocks.js @@ -1,3 +1,5 @@ +/* global Node */ + /** * Internal dependencies */ @@ -5,10 +7,10 @@ import { isEmpty } from './utils'; import { isPhrasingContent } from './phrasing-content'; /** - * Browser dependencies + * @param {string} HTML HTML to parse. + * + * @return {string} Result. */ -const { ELEMENT_NODE, TEXT_NODE } = window.Node; - export default function( HTML ) { const decuDoc = document.implementation.createHTMLDocument( '' ); const accuDoc = document.implementation.createHTMLDocument( '' ); @@ -22,7 +24,7 @@ export default function( HTML ) { const node = decu.firstChild; // Text nodes: wrap in a paragraph, or append to previous. - if ( node.nodeType === TEXT_NODE ) { + if ( node.nodeType === Node.TEXT_NODE ) { if ( ! node.nodeValue.trim() ) { decu.removeChild( node ); } else { @@ -33,7 +35,7 @@ export default function( HTML ) { accu.lastChild.appendChild( node ); } // Element nodes. - } else if ( node.nodeType === ELEMENT_NODE ) { + } else if ( node.nodeType === Node.ELEMENT_NODE ) { // BR nodes: create a new paragraph on double, or append to previous. if ( node.nodeName === 'BR' ) { if ( node.nextSibling && node.nextSibling.nodeName === 'BR' ) { diff --git a/packages/blocks/src/api/raw-handling/paste-handler.js b/packages/blocks/src/api/raw-handling/paste-handler.js index c99b4ea5c5d0a..5af0d95560546 100644 --- a/packages/blocks/src/api/raw-handling/paste-handler.js +++ b/packages/blocks/src/api/raw-handling/paste-handler.js @@ -37,6 +37,15 @@ import { */ const { console } = window; +/** + * @template {Record} T + * @typedef {import('@wordpress/blocks').BlockInstance} BlockInstance + */ + +/** + * @typedef {import('@wordpress/blocks').TransformRaw} RawTransform + */ + /** * Filters HTML to only contain phrasing content. * @@ -55,13 +64,14 @@ function filterInlineHTML( HTML ) { } function getRawTransformations() { - return filter( getBlockTransforms( 'from' ), { type: 'raw' } ) - .map( ( transform ) => { - return transform.isMatch ? transform : { - ...transform, - isMatch: ( node ) => transform.selector && node.matches( transform.selector ), - }; - } ); + return /** @type {RawTransform[]} */ ( + filter( getBlockTransforms( 'from' ), { type: 'raw' } ) + ).map( ( transform ) => { + return transform.isMatch ? transform : { + ...transform, + isMatch: ( node ) => transform.selector && node.matches( transform.selector ), + }; + } ); } /** @@ -69,11 +79,11 @@ function getRawTransformations() { * top-level tag. The HTML should be filtered to not have any text between * top-level tags and formatted in a way that blocks can handle the HTML. * - * @param {Object} $1 Named parameters. - * @param {string} $1.html HTML to convert. - * @param {Array} $1.rawTransforms Transforms that can be used. + * @param {Object} params Named parameters. + * @param {string} params.html HTML to convert. + * @param {RawTransform[]} params.rawTransforms Transforms that can be used. * - * @return {Array} An array of blocks. + * @return {Array>} An array of blocks. */ function htmlToBlocks( { html, rawTransforms } ) { const doc = document.implementation.createHTMLDocument( '' ); @@ -81,7 +91,7 @@ function htmlToBlocks( { html, rawTransforms } ) { doc.body.innerHTML = html; return Array.from( doc.body.children ).map( ( node ) => { - const rawTransform = findTransform( rawTransforms, ( { isMatch } ) => isMatch( node ) ); + const rawTransform = findTransform( rawTransforms, ( { isMatch } ) => isMatch && isMatch( node ) ); if ( ! rawTransform ) { return createBlock( @@ -113,6 +123,7 @@ function htmlToBlocks( { html, rawTransforms } ) { /** * Converts an HTML string to known blocks. Strips everything else. * + * @param {Object} options * @param {string} [options.HTML] The HTML to convert. * @param {string} [options.plainText] Plain text version. * @param {string} [options.mode] Handle content as blocks or inline content. @@ -122,7 +133,7 @@ function htmlToBlocks( { html, rawTransforms } ) { * @param {Array} [options.tagName] The tag into which content will be inserted. * @param {boolean} [options.canUserUseUnfilteredHTML] Whether or not the user can use unfiltered HTML. * - * @return {Array|string} A list of blocks or a string, depending on `handlerMode`. + * @return {string|Array>} A list of blocks or a string, depending on `handlerMode`. */ export function pasteHandler( { HTML = '', plainText = '', mode = 'AUTO', tagName, canUserUseUnfilteredHTML = false } ) { // First of all, strip any meta tags. diff --git a/packages/blocks/src/api/raw-handling/phrasing-content-reducer.js b/packages/blocks/src/api/raw-handling/phrasing-content-reducer.js index 5c5ef223298fe..d95378fdeeeb5 100644 --- a/packages/blocks/src/api/raw-handling/phrasing-content-reducer.js +++ b/packages/blocks/src/api/raw-handling/phrasing-content-reducer.js @@ -3,6 +3,10 @@ */ import { wrap, replaceTag } from '@wordpress/dom'; +// eslint-disable-next-line valid-jsdoc +/** + * @type {import('./').NodeFilterFunc} + */ export default function( node, doc ) { // In jsdom-jscore, 'node.style' can be null. // TODO: Explore fixing this by patching jsdom-jscore. diff --git a/packages/blocks/src/api/raw-handling/phrasing-content.js b/packages/blocks/src/api/raw-handling/phrasing-content.js index 028c61e9b8958..62b12a045abcf 100644 --- a/packages/blocks/src/api/raw-handling/phrasing-content.js +++ b/packages/blocks/src/api/raw-handling/phrasing-content.js @@ -3,6 +3,10 @@ */ import { omit } from 'lodash'; +/** + * @typedef {import('@wordpress/blocks').PhrasingContentSchema} PhrasingContentSchema + */ + const phrasingContentSchema = { strong: {}, em: {}, @@ -30,10 +34,10 @@ const phrasingContentSchema = { * * @see https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Phrasing_content * - * @return {Object} Schema. + * @return {PhrasingContentSchema} Schema. */ export function getPhrasingContentSchema() { - return phrasingContentSchema; + return /** @type {any} */( phrasingContentSchema ); } /** @@ -41,7 +45,7 @@ export function getPhrasingContentSchema() { * * @see https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Phrasing_content * - * @param {Element} node The node to test. + * @param {Node} node The node to test. * * @return {boolean} True if phrasing content, false if not. */ diff --git a/packages/blocks/src/api/raw-handling/shortcode-converter.js b/packages/blocks/src/api/raw-handling/shortcode-converter.js index 5385f0aed4150..099fff1c76af0 100644 --- a/packages/blocks/src/api/raw-handling/shortcode-converter.js +++ b/packages/blocks/src/api/raw-handling/shortcode-converter.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { some, castArray, first, mapValues, pickBy, includes } from 'lodash'; +import { some, castArray, mapValues, pickBy, includes } from 'lodash'; /** * WordPress dependencies @@ -15,21 +15,26 @@ import { createBlock, getBlockTransforms, findTransform } from '../factory'; import { getBlockType } from '../registration'; import { getBlockAttributes } from '../parser'; +/** + * @typedef {import('@wordpress/blocks').TransformShortcode>} TransformShortcode + */ + function segmentHTMLToShortcodeBlock( HTML, lastIndex = 0 ) { // Get all matches. const transformsFrom = getBlockTransforms( 'from' ); - const transformation = findTransform( transformsFrom, ( transform ) => ( - transform.type === 'shortcode' && - some( castArray( transform.tag ), ( tag ) => regexp( tag ).test( HTML ) ) - ) ); + const transformation = /** @type {TransformShortcode|null} */ ( + findTransform( transformsFrom, ( transform ) => ( + transform.type === 'shortcode' && + some( castArray( transform.tag ), ( tag ) => regexp( tag ).test( HTML ) ) + ) ) + ); if ( ! transformation ) { return [ HTML ]; } - const transformTags = castArray( transformation.tag ); - const transformTag = first( transformTags ); + const transformTag = castArray( transformation.tag )[ 0 ]; let match; diff --git a/packages/blocks/src/api/raw-handling/special-comment-converter.js b/packages/blocks/src/api/raw-handling/special-comment-converter.js index 4564c5be0b0fe..8e7dc838e734b 100644 --- a/packages/blocks/src/api/raw-handling/special-comment-converter.js +++ b/packages/blocks/src/api/raw-handling/special-comment-converter.js @@ -1,13 +1,11 @@ +/* global Node */ + /** * WordPress dependencies */ import { remove, replace } from '@wordpress/dom'; -/** - * Browser dependencies - */ -const { COMMENT_NODE } = window.Node; - +// eslint-disable-next-line valid-jsdoc /** * Looks for `` and `` comments, as well as the * `` variant and its `` companion, @@ -20,12 +18,10 @@ const { COMMENT_NODE } = window.Node; * The custom element is then expected to be recognized by any registered * block's `raw` transform. * - * @param {Node} node The node to be processed. - * @param {Document} doc The document of the node. - * @return {void} + * @type {import('./').NodeFilterFunc} */ export default function( node, doc ) { - if ( node.nodeType !== COMMENT_NODE ) { + if ( node.nodeType !== Node.COMMENT_NODE ) { return; } @@ -47,7 +43,7 @@ export default function( node, doc ) { let noTeaser = false; while ( ( sibling = sibling.nextSibling ) ) { if ( - sibling.nodeType === COMMENT_NODE && + sibling.nodeType === Node.COMMENT_NODE && sibling.nodeValue === 'noteaser' ) { noTeaser = true; diff --git a/packages/blocks/src/api/raw-handling/utils.js b/packages/blocks/src/api/raw-handling/utils.js index 896bb6b8d4a5e..d9d5cee11e055 100644 --- a/packages/blocks/src/api/raw-handling/utils.js +++ b/packages/blocks/src/api/raw-handling/utils.js @@ -1,3 +1,5 @@ +/* global Node */ + /** * External dependencies */ @@ -15,10 +17,10 @@ import { hasBlockSupport } from '..'; import { isPhrasingContent } from './phrasing-content'; /** - * Browser dependencies + * @typedef {import('./').NodeFilterFunc} NodeFilterFunc */ -const { ELEMENT_NODE, TEXT_NODE } = window.Node; +// FIXME: The return type should be strongly typed. Need to discuss the exact interface of this. /** * Given raw transforms from blocks, merges all schemas into one. * @@ -82,28 +84,28 @@ export function getBlockContentSchema( transforms ) { * Recursively checks if an element is empty. An element is not empty if it * contains text or contains elements with attributes such as images. * - * @param {Element} element The element to check. + * @param {Node} node The node to check. * * @return {boolean} Wether or not the element is empty. */ -export function isEmpty( element ) { - if ( ! element.hasChildNodes() ) { +export function isEmpty( node ) { + if ( ! node.hasChildNodes() ) { return true; } - return Array.from( element.childNodes ).every( ( node ) => { - if ( node.nodeType === TEXT_NODE ) { - return ! node.nodeValue.trim(); + return Array.from( node.childNodes ).every( ( childNode ) => { + if ( childNode.nodeType === Node.TEXT_NODE ) { + return ! childNode.nodeValue.trim(); } - if ( node.nodeType === ELEMENT_NODE ) { - if ( node.nodeName === 'BR' ) { + if ( childNode.nodeType === Node.ELEMENT_NODE ) { + if ( childNode.nodeName === 'BR' ) { return true; - } else if ( node.hasAttributes() ) { + } else if ( childNode.hasAttributes() ) { return false; } - return isEmpty( node ); + return isEmpty( childNode ); } return true; @@ -125,10 +127,10 @@ export function isPlain( HTML ) { /** * Given node filters, deeply filters and mutates a NodeList. * - * @param {NodeList} nodeList The nodeList to filter. - * @param {Array} filters An array of functions that can mutate with the provided node. - * @param {Document} doc The document of the nodeList. - * @param {Object} schema The schema to use. + * @param {NodeListOf} nodeList The nodeList to filter. + * @param {NodeFilterFunc[]} filters An array of functions that can mutate the provided node. + * @param {Document} doc The document of the nodeList. + * @param {Object} schema The schema to use. */ export function deepFilterNodeList( nodeList, filters, doc, schema ) { Array.from( nodeList ).forEach( ( node ) => { @@ -149,9 +151,9 @@ export function deepFilterNodeList( nodeList, filters, doc, schema ) { * Given node filters, deeply filters HTML tags. * Filters from the deepest nodes to the top. * - * @param {string} HTML The HTML to filter. - * @param {Array} filters An array of functions that can mutate with the provided node. - * @param {Object} schema The schema to use. + * @param {string} HTML The HTML to filter. + * @param {NodeFilterFunc[]} [filters=[]] An array of functions that can mutate with the provided node. + * @param {Object} schema The schema to use. * * @return {string} The filtered HTML. */ @@ -169,10 +171,10 @@ export function deepFilterHTML( HTML, filters = [], schema ) { * Given a schema, unwraps or removes nodes, attributes and classes on a node * list. * - * @param {NodeList} nodeList The nodeList to filter. - * @param {Document} doc The document of the nodeList. - * @param {Object} schema An array of functions that can mutate with the provided node. - * @param {Object} inline Whether to clean for inline mode. + * @param {NodeListOf} nodeList The nodeList to filter. + * @param {Document} doc The document of the nodeList. + * @param {Object} schema An array of functions that can mutate with the provided node. + * @param {boolean} [inline] Whether to clean for inline mode. */ function cleanNodeList( nodeList, doc, schema, inline ) { Array.from( nodeList ).forEach( ( node ) => { @@ -184,7 +186,7 @@ function cleanNodeList( nodeList, doc, schema, inline ) { schema.hasOwnProperty( tag ) && ( ! schema[ tag ].isMatch || schema[ tag ].isMatch( node ) ) ) { - if ( node.nodeType === ELEMENT_NODE ) { + if ( node.nodeType === Node.ELEMENT_NODE ) { const { attributes = [], classes = [], @@ -288,9 +290,9 @@ function cleanNodeList( nodeList, doc, schema, inline ) { /** * Given a schema, unwraps or removes nodes, attributes and classes on HTML. * - * @param {string} HTML The HTML to clean up. - * @param {Object} schema Schema for the HTML. - * @param {Object} inline Whether to clean for inline mode. + * @param {string} HTML The HTML to clean up. + * @param {Object} schema Schema for the HTML. + * @param {boolean} [inline] Whether to clean for inline mode. * * @return {string} The cleaned up HTML. */ diff --git a/packages/blocks/src/api/registration.js b/packages/blocks/src/api/registration.js index 498f83627e6ff..428599f2730af 100644 --- a/packages/blocks/src/api/registration.js +++ b/packages/blocks/src/api/registration.js @@ -22,64 +22,24 @@ import { select, dispatch } from '@wordpress/data'; import { isValidIcon, normalizeIconObject } from './utils'; /** - * Render behavior of a block type icon; one of a Dashicon slug, an element, - * or a component. - * - * @typedef {(string|WPElement|WPComponent)} WPBlockTypeIconRender - * - * @see https://developer.wordpress.org/resource/dashicons/ + * @typedef {import('@wordpress/blocks').Block>} BlockType */ /** - * An object describing a normalized block type icon. - * - * @typedef {WPBlockTypeIconDescriptor} - * - * @property {WPBlockTypeIconRender} src Render behavior of the icon, - * one of a Dashicon slug, an - * element, or a component. - * @property {string} background Optimal background hex string - * color when displaying icon. - * @property {string} foreground Optimal foreground hex string - * color when displaying icon. - * @property {string} shadowColor Optimal shadow hex string - * color when displaying icon. + * @typedef {import('@wordpress/blocks').BlockInstance>} BlockInstance */ /** - * 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 + * @template {Record} T + * @typedef {import('@wordpress/blocks').BlockConfiguration} BlockConfiguration */ /** - * Defined behavior of a block type. - * - * @typedef {WPBlockType} - * - * @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. + * @typedef {import('@wordpress/blocks').BlockSupports} BlockSupports */ /** - * Default values to assign for omitted optional block type settings. - * - * @type {Object} + * @typedef {import('@wordpress/blocks').BlockStyle} BlockStyle */ const DEFAULT_BLOCK_TYPE_SETTINGS = { icon: 'block-default', @@ -107,11 +67,12 @@ 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. + * @template {Record} T + * @param {string} name Block name. + * @param {BlockConfiguration} settings Block settings. * - * @return {?WPBlock} The block, if it has been successfully registered; - * otherwise `undefined`. + * @return {BlockConfiguration|undefined} The block, if it has been successfully registered; + * otherwise `undefined`. */ export function registerBlockType( name, settings ) { settings = { @@ -212,8 +173,8 @@ export function registerBlockType( name, settings ) { * * @param {string} name Block name. * - * @return {?WPBlock} The previous block value, if it has been successfully - * unregistered; otherwise `undefined`. + * @return {BlockType|undefined} The previous block value, if it has been successfully + * unregistered; otherwise `undefined`. */ export function unregisterBlockType( name ) { const oldBlock = select( 'core/blocks' ).getBlockType( name ); @@ -240,7 +201,7 @@ export function setFreeformContentHandlerName( blockName ) { * Retrieves name of block handling non-block content, or undefined if no * handler has been defined. * - * @return {?string} Block name. + * @return {string|undefined} Block name. */ export function getFreeformContentHandlerName() { return select( 'core/blocks' ).getFreeformFallbackBlockName(); @@ -249,7 +210,7 @@ export function getFreeformContentHandlerName() { /** * Retrieves name of block used for handling grouping interactions. * - * @return {?string} Block name. + * @return {string|undefined} Block name. */ export function getGroupingBlockName() { return select( 'core/blocks' ).getGroupingBlockName(); @@ -268,7 +229,7 @@ export function setUnregisteredTypeHandlerName( blockName ) { * Retrieves name of block handling unregistered block types, or undefined if no * handler has been defined. * - * @return {?string} Block name. + * @return {string|undefined} Block name. */ export function getUnregisteredTypeHandlerName() { return select( 'core/blocks' ).getUnregisteredFallbackBlockName(); @@ -295,7 +256,7 @@ export function setGroupingBlockName( name ) { /** * Retrieves the default block name. * - * @return {?string} Block name. + * @return {string|undefined} Block name. */ export function getDefaultBlockName() { return select( 'core/blocks' ).getDefaultBlockName(); @@ -306,42 +267,45 @@ export function getDefaultBlockName() { * * @param {string} name Block name. * - * @return {?Object} Block type. + * @return {BlockType|undefined} Block type. */ export function getBlockType( name ) { return select( 'core/blocks' ).getBlockType( name ); } +// eslint-disable-next-line valid-jsdoc /** * Returns all registered blocks. * - * @return {Array} Block settings. + * @return {readonly BlockType[]} Block settings. */ export function getBlockTypes() { return select( 'core/blocks' ).getBlockTypes(); } +// eslint-disable-next-line valid-jsdoc /** * Returns the block support value for a feature, if defined. * - * @param {(string|Object)} nameOrType Block name or type object - * @param {string} feature Feature to retrieve - * @param {*} defaultSupports Default value to return if not - * explicitly defined + * @param {string|BlockType} nameOrType Block name or type object. + * @param {keyof BlockSupports} feature Feature to retrieve. + * @param {any} [defaultSupports] Default value to return if not + * explicitly defined. * - * @return {?*} Block support value + * @return {any} Block support value. */ export function getBlockSupport( nameOrType, feature, defaultSupports ) { return select( 'core/blocks' ).getBlockSupport( nameOrType, feature, defaultSupports ); } +// eslint-disable-next-line valid-jsdoc /** * Returns true if the block defines support for a feature, or false otherwise. * - * @param {(string|Object)} nameOrType Block name or type object. - * @param {string} feature Feature to test. - * @param {boolean} defaultSupports Whether feature is supported by - * default if not explicitly defined. + * @param {string|BlockType} nameOrType Block name or type object. + * @param {keyof BlockSupports} feature Feature to test. + * @param {boolean} [defaultSupports] Whether feature is supported by default + * if not explicitly defined. * * @return {boolean} Whether block supports feature. */ @@ -354,7 +318,7 @@ export function hasBlockSupport( nameOrType, feature, defaultSupports ) { * special block type that is used to point to a global block stored via the * API. * - * @param {Object} blockOrType Block or Block Type to test. + * @param {BlockInstance|BlockType} blockOrType Block or Block Type to test. * * @return {boolean} Whether the given block is a reusable block. */ @@ -365,9 +329,9 @@ export function isReusableBlock( blockOrType ) { /** * Returns an array with the child blocks of a given block. * - * @param {string} blockName Name of block (example: “latest-posts”). + * @param {string} blockName Name of block (example: 'core/columns'). * - * @return {Array} Array of child block names. + * @return {string[]} Array of child block names. */ export const getChildBlockNames = ( blockName ) => { return select( 'core/blocks' ).getChildBlockNames( blockName ); @@ -376,7 +340,7 @@ export const getChildBlockNames = ( blockName ) => { /** * Returns a boolean indicating if a block has child blocks or not. * - * @param {string} blockName Name of block (example: “latest-posts”). + * @param {string} blockName Name of block (example: 'core/latest-posts'). * * @return {boolean} True if a block contains child blocks and false otherwise. */ @@ -399,8 +363,10 @@ export const hasChildBlocksWithInserterSupport = ( blockName ) => { /** * Registers a new block style variation for the given block. * - * @param {string} blockName Name of block (example: “core/latest-posts”). - * @param {Object} styleVariation Object containing `name` which is the class name applied to the block and `label` which identifies the variation to the user. + * @param {string} blockName Name of block (example: 'core/paragraph'). + * @param {BlockStyle} styleVariation Object containing `name` which is the class + * name applied to the block and `label` which + * identifies the variation to the user. */ export const registerBlockStyle = ( blockName, styleVariation ) => { dispatch( 'core/blocks' ).addBlockStyles( blockName, styleVariation ); @@ -409,7 +375,7 @@ export const registerBlockStyle = ( blockName, styleVariation ) => { /** * Unregisters a block style variation for the given block. * - * @param {string} blockName Name of block (example: “core/latest-posts”). + * @param {string} blockName Name of block (example: 'core/latest-posts'). * @param {string} styleVariationName Name of class applied to the block. */ export const unregisterBlockStyle = ( blockName, styleVariationName ) => { diff --git a/packages/blocks/src/api/serializer.js b/packages/blocks/src/api/serializer.js index 1776a32e36299..e15aef3cb374e 100644 --- a/packages/blocks/src/api/serializer.js +++ b/packages/blocks/src/api/serializer.js @@ -22,9 +22,19 @@ import { normalizeBlockType } from './utils'; import BlockContentProvider from '../block-content-provider'; /** - * @typedef {Object} WPBlockSerializationOptions Serialization Options. - * - * @property {boolean} isInnerBlocks Whether we are serializing inner blocks. + * @typedef {import('@wordpress/blocks').Block>} BlockType + */ + +/** + * @typedef {import('@wordpress/blocks').BlockInstance>} BlockInstance + */ + +/** + * @typedef {import('@wordpress/element').ReactChild} ReactChild + */ + +/** + * @typedef {import('@wordpress/element').ReactElement} ReactElement */ /** @@ -57,15 +67,16 @@ export function getBlockMenuDefaultClassName( blockName ) { return applyFilters( 'blocks.getBlockMenuDefaultClassName', className, blockName ); } +// eslint-disable-next-line valid-jsdoc /** * Given a block type containing a save render implementation and attributes, returns the * enhanced element to be saved or string when raw HTML expected. * - * @param {string|Object} blockTypeOrName Block type or name. - * @param {Object} attributes Block attributes. - * @param {?Array} innerBlocks Nested blocks. + * @param {string|BlockType} blockTypeOrName Block type or name. + * @param {Record} attributes Block attributes. + * @param {readonly BlockInstance[]} [innerBlocks=[]] Nested blocks. * - * @return {Object|string} Save element or raw HTML string. + * @return {ReactChild} Save element or raw HTML string. */ export function getSaveElement( blockTypeOrName, attributes, innerBlocks = [] ) { const blockType = normalizeBlockType( blockTypeOrName ); @@ -85,9 +96,9 @@ export function getSaveElement( blockTypeOrName, attributes, innerBlocks = [] ) /** * Filters the props applied to the block save result element. * - * @param {Object} props Props applied to save element. - * @param {WPBlockType} blockType Block type definition. - * @param {Object} attributes Block attributes. + * @param {Object} props Props applied to save element. + * @param {BlockType} blockType Block type definition. + * @param {Object} attributes Block attributes. */ const props = applyFilters( 'blocks.getSaveContent.extraProps', @@ -104,9 +115,9 @@ export function getSaveElement( blockTypeOrName, attributes, innerBlocks = [] ) /** * Filters the save result of a block during serialization. * - * @param {WPElement} element Block save result. - * @param {WPBlockType} blockType Block type definition. - * @param {Object} attributes Block attributes. + * @param {ReactElement} element Block save result. + * @param {BlockType} blockType Block type definition. + * @param {Object} attributes Block attributes. */ element = applyFilters( 'blocks.getSaveElement', element, blockType, attributes ); @@ -117,13 +128,14 @@ export function getSaveElement( blockTypeOrName, attributes, innerBlocks = [] ) ); } +// eslint-disable-next-line valid-jsdoc /** * Given a block type containing a save render implementation and attributes, returns the * static markup to be saved. * - * @param {string|Object} blockTypeOrName Block type or name. - * @param {Object} attributes Block attributes. - * @param {?Array} innerBlocks Nested blocks. + * @param {string|BlockType} blockTypeOrName Block type or name. + * @param {Record} attributes Block attributes. + * @param {readonly BlockInstance[]} [innerBlocks] Nested blocks. * * @return {string} Save content. */ @@ -144,10 +156,10 @@ export function getSaveContent( blockTypeOrName, attributes, innerBlocks ) { * This function returns only those attributes which are needed to persist and * which cannot be matched from the block content. * - * @param {Object} blockType Block type. - * @param {Object} attributes Attributes from in-memory block data. + * @param {BlockType} blockType Block type. + * @param {Record} attributes Attributes from in-memory block data. * - * @return {Object} Subset of attributes for comment serialization. + * @return {Record} Subset of attributes for comment serialization. */ export function getCommentAttributes( blockType, attributes ) { return reduce( blockType.attributes, ( result, attributeSchema, key ) => { @@ -179,7 +191,7 @@ export function getCommentAttributes( blockType, attributes ) { * Given an attributes object, returns a string in the serialized attributes * format prepared for post content. * - * @param {Object} attributes Attributes object. + * @param {Record} attributes Attributes object. * * @return {string} Serialized attributes. */ @@ -203,7 +215,7 @@ export function serializeAttributes( attributes ) { /** * Given a block object, returns the Block's Inner HTML markup. * - * @param {Object} block Block instance. + * @param {BlockInstance} block Block instance. * * @return {string} HTML. */ @@ -222,15 +234,15 @@ export function getBlockContent( block ) { } catch ( error ) {} } - return saveContent; + return saveContent || ''; } /** * Returns the content of a block, including comment delimiters. * - * @param {string} rawBlockName Block name. - * @param {Object} attributes Block attributes. - * @param {string} content Block save content. + * @param {string} rawBlockName Block name. + * @param {Record} attributes Block attributes. + * @param {string} content Block save content. * * @return {string} Comment-delimited block content. */ @@ -261,8 +273,9 @@ export function getCommentDelimitedContent( rawBlockName, attributes, content ) * Returns the content of a block, including comment delimiters, determining * serialized attributes and content form from the current state of the block. * - * @param {Object} block Block instance. - * @param {WPBlockSerializationOptions} options Serialization options. + * @param {BlockInstance} block Block instance. + * @param {Object} [options={}] + * @param {boolean} [options.isInnerBlocks=false] * * @return {string} Serialized block. */ @@ -282,11 +295,11 @@ export function serializeBlock( block, { isInnerBlocks = false } = {} ) { return getCommentDelimitedContent( blockName, saveAttributes, saveContent ); } +// eslint-disable-next-line valid-jsdoc /** * Takes a block or set of blocks and returns the serialized post content. * - * @param {Array} blocks Block(s) to serialize. - * @param {WPBlockSerializationOptions} options Serialization options. + * @param {readonly BlockInstance[]} blocks Block instance(s) to serialize. * * @return {string} The post content. */ diff --git a/packages/blocks/src/api/templates.js b/packages/blocks/src/api/templates.js index d09be49453e70..59542f0c2eabc 100644 --- a/packages/blocks/src/api/templates.js +++ b/packages/blocks/src/api/templates.js @@ -14,13 +14,21 @@ import { renderToString } from '@wordpress/element'; import { createBlock } from './factory'; import { getBlockType } from './registration'; +/** + * @typedef {import('@wordpress/blocks').BlockInstance>} BlockInstance + */ + +/** + * @typedef {import('@wordpress/blocks').TemplateArray} TemplateArray + */ + /** * Checks whether a list of blocks matches a template by comparing the block names. * - * @param {Array} blocks Block list. - * @param {Array} template Block template. + * @param {BlockInstance[]} [blocks=[]] Block list. + * @param {TemplateArray} [template=[]] Block template. * - * @return {boolean} Whether the list of blocks matches a templates + * @return {boolean} Whether the list of blocks matches a templates. */ export function doBlocksMatchTemplate( blocks = [], template = [] ) { return ( @@ -43,10 +51,10 @@ export function doBlocksMatchTemplate( blocks = [], template = [] ) { * (If it has the same name) and if doesn't match, we create a new block based on the template. * Extra blocks not present in the template are removed. * - * @param {Array} blocks Block list. - * @param {Array} template Block template. + * @param {BlockInstance[]} [blocks=[]] Block list. + * @param {TemplateArray} [template] Block template. * - * @return {Array} Updated Block list. + * @return {BlockInstance[]} Updated Block list. */ export function synchronizeBlocksWithTemplate( blocks = [], template ) { // If no template is provided, return blocks unmodified. diff --git a/packages/blocks/src/api/utils.js b/packages/blocks/src/api/utils.js index 99dc98b5c3204..76ab2f18c4d69 100644 --- a/packages/blocks/src/api/utils.js +++ b/packages/blocks/src/api/utils.js @@ -15,22 +15,33 @@ import { Component, isValidElement } from '@wordpress/element'; import { getBlockType, getDefaultBlockName } from './registration'; import { createBlock } from './factory'; +/** + * @typedef {import('@wordpress/blocks').Block>} BlockType + */ + +/** + * @typedef {import('@wordpress/blocks').BlockIcon} BlockIcon + */ + +/** + * @typedef {import('@wordpress/blocks').BlockInstance>} BlockInstance + */ + /** * Array of icon colors containing a color to be used if the icon color * was not explicitly set but the icon background color was. * - * @type {Object} + * @type {readonly string[]} */ const ICON_COLORS = [ '#191e23', '#f8f9f9' ]; /** - * Determines whether the block is a default block - * and its attributes are equal to the default attributes - * which means the block is unmodified. + * Determines whether the block is a default block and its attributes are equal + * to the default attributes which means the block is unmodified. * - * @param {WPBlock} block Block Object + * @param {BlockInstance} block Block instance. * - * @return {boolean} Whether the block is an unmodified default block + * @return {boolean} Whether the block is an unmodified default block. */ export function isUnmodifiedDefaultBlock( block ) { const defaultBlockName = getDefaultBlockName(); @@ -50,7 +61,7 @@ export function isUnmodifiedDefaultBlock( block ) { const newDefaultBlock = isUnmodifiedDefaultBlock.block; const blockType = getBlockType( defaultBlockName ); - return every( blockType.attributes, ( value, key ) => + return every( blockType.attributes, ( _, key ) => newDefaultBlock.attributes[ key ] === block.attributes[ key ] ); } @@ -58,11 +69,10 @@ export function isUnmodifiedDefaultBlock( block ) { /** * Function that checks if the parameter is a valid icon. * - * @param {*} icon Parameter to be checked. + * @param {any} icon Parameter to be checked. * * @return {boolean} True if the parameter is a valid icon and false otherwise. */ - export function isValidIcon( icon ) { return !! icon && ( isString( icon ) || @@ -77,18 +87,18 @@ export function isValidIcon( icon ) { * and returns a new icon object that is normalized so we can rely on just on possible icon structure * in the codebase. * - * @param {WPBlockTypeIconRender} icon Render behavior of a block type icon; - * one of a Dashicon slug, an element, or a - * component. + * @param {BlockIcon} icon Slug of the Dashicon to be shown + * as the icon for the block in the + * inserter, or element or an object describing the icon. * - * @return {WPBlockTypeIconDescriptor} Object describing the icon. + * @return {BlockIcon} Object describing the icon. */ export function normalizeIconObject( icon ) { if ( isValidIcon( icon ) ) { return { src: icon }; } - if ( has( icon, [ 'background' ] ) ) { + if ( typeof icon === 'object' && has( icon, [ 'background' ] ) ) { const tinyBgColor = tinycolor( icon.background ); return { @@ -110,9 +120,9 @@ export function normalizeIconObject( icon ) { * it converts it to the matching block type object. * It passes the original object otherwise. * - * @param {string|Object} blockTypeOrName Block type or name. + * @param {string|BlockType} blockTypeOrName Block type or name. * - * @return {?Object} Block type. + * @return {BlockType|undefined} Block type. */ export function normalizeBlockType( blockTypeOrName ) { if ( isString( blockTypeOrName ) ) { diff --git a/packages/blocks/src/api/validation.js b/packages/blocks/src/api/validation.js index ff37b5a73872c..4dd24d2d99845 100644 --- a/packages/blocks/src/api/validation.js +++ b/packages/blocks/src/api/validation.js @@ -22,6 +22,22 @@ import { decodeEntities } from '@wordpress/html-entities'; import { getSaveContent } from './serializer'; import { normalizeBlockType } from './utils'; +/** + * @typedef {import('@wordpress/blocks').Block>} BlockType + */ + +/** + * @typedef {(message: string, ...args: any[]) => void} Logger + */ + +/** + * @typedef {import('simple-html-tokenizer').StartTag} StartTag + */ + +/** + * @typedef {import('simple-html-tokenizer').Token} Token + */ + /** * Globally matches any consecutive whitespace * @@ -56,7 +72,7 @@ const REGEXP_STYLE_URL_TYPE = /^url\s*\(['"\s]*(.*?)['"\s]*\)$/; * [ tr.firstChild.textContent.trim() ]: true * } ), {} ) ).sort(); * - * @type {Array} + * @type {readonly string[]} */ const BOOLEAN_ATTRIBUTES = [ 'allowfullscreen', @@ -97,13 +113,15 @@ const BOOLEAN_ATTRIBUTES = [ * See: https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#enumerated-attribute * Extracted from: https://html.spec.whatwg.org/multipage/indices.html#attributes-3 * + * ```js * Object.keys( [ ...document.querySelectorAll( '#attributes-1 > tbody > tr' ) ] * .filter( ( tr ) => /^("(.+?)";?\s*)+/.test( tr.lastChild.textContent.trim() ) ) * .reduce( ( result, tr ) => Object.assign( result, { * [ tr.firstChild.textContent.trim() ]: true * } ), {} ) ).sort(); + * ``` * - * @type {Array} + * @type {readonly string[]} */ const ENUMERATED_ATTRIBUTES = [ 'autocapitalize', @@ -134,7 +152,7 @@ const ENUMERATED_ATTRIBUTES = [ * Meaningful attributes are those who cannot be safely ignored when omitted in * one HTML markup string and not another. * - * @type {Array} + * @type {readonly string[]} */ const MEANINGFUL_ATTRIBUTES = [ ...BOOLEAN_ATTRIBUTES, @@ -146,7 +164,7 @@ const MEANINGFUL_ATTRIBUTES = [ * behavior for consideration in text token equivalence, carefully ordered from * least-to-most expensive operations. * - * @type {Array} + * @type {ReadonlyArray<(text: string) => string>} */ const TEXT_NORMALIZATIONS = [ identity, @@ -163,7 +181,7 @@ const TEXT_NORMALIZATIONS = [ * * Tested aginst "12.5 Named character references": * - * ``` + * ```js * const references = [ ...document.querySelectorAll( * '#named-character-references-table tr[id^=entity-] td:first-child' * ) ].map( ( code ) => code.textContent ) @@ -233,12 +251,12 @@ export class DecodeEntityParser { * * @param {string} entity Entity fragment discovered in HTML. * - * @return {?string} Entity substitute value. + * @return {string|undefined} Entity substitute value. */ parse( entity ) { - if ( isValidCharacterReference( entity ) ) { - return decodeEntities( '&' + entity + ';' ); - } + return isValidCharacterReference( entity ) ? + decodeEntities( '&' + entity + ';' ) : + undefined; } } @@ -249,9 +267,9 @@ const log = ( () => { /** * Creates a logger with block validation prefix. * - * @param {Function} logger Original logger function. + * @param {Logger} logger Original logger function. * - * @return {Function} Augmented logger function. + * @return {Logger} Augmented logger function. */ function createLogger( logger ) { // In test environments, pre-process the sprintf message to improve @@ -301,6 +319,7 @@ export function getTextWithCollapsedWhitespace( text ) { return getTextPiecesSplitOnWhitespace( text ).join( ' ' ); } +// eslint-disable-next-line valid-jsdoc /** * Returns attribute pairs of the given StartTag token, including only pairs * where the value is non-empty or the attribute is a boolean attribute, an @@ -308,9 +327,9 @@ export function getTextWithCollapsedWhitespace( text ) { * * @see MEANINGFUL_ATTRIBUTES * - * @param {Object} token StartTag token. + * @param {StartTag} token StartTag token. * - * @return {Array[]} Attribute pairs. + * @return {StartTag['attributes']} Attribute pairs. */ export function getMeaningfulAttributePairs( token ) { return token.attributes.filter( ( pair ) => { @@ -375,9 +394,12 @@ export function getNormalizedStyleValue( value ) { * * @param {string} text Style attribute. * - * @return {Object} Style properties. + * @return {Record} Style properties. */ export function getStyleProperties( text ) { + /** + * @type {Array<[string,string]>} + */ const pairs = text // Trim ending semicolon (avoid including in split) .replace( /;?\s*$/, '' ) @@ -404,25 +426,43 @@ export function getStyleProperties( text ) { * @type {Object} */ export const isEqualAttributesOfName = { + /** + * Return whether or not two class attributes are equal. + * + * @param {string} actual + * @param {string} expected + * + * @return {boolean} `true` if attrbutes are equal, `false` otherwise. + */ class: ( actual, expected ) => { // Class matches if members are the same, even if out of order or // superfluous whitespace between. return ! xor( ...[ actual, expected ].map( getTextPiecesSplitOnWhitespace ) ).length; }, + /** + * Return whether or not two style attributes are equal. + * + * @param {string} actual + * @param {string} expected + * + * @return {boolean} `true` if attrbutes are equal, `false` otherwise. + */ style: ( actual, expected ) => { - return isEqual( ...[ actual, expected ].map( getStyleProperties ) ); + const [ a, e ] = [ actual, expected ].map( getStyleProperties ); + return isEqual( a, e ); }, // For each boolean attribute, mere presence of attribute in both is enough // to assume equivalence. ...fromPairs( BOOLEAN_ATTRIBUTES.map( ( attribute ) => [ attribute, stubTrue ] ) ), }; +// eslint-disable-next-line valid-jsdoc /** * Given two sets of attribute tuples, returns true if the attribute sets are * equivalent. * - * @param {Array[]} actual Actual attributes tuples. - * @param {Array[]} expected Expected attributes tuples. + * @param {StartTag['attributes']} actual Actual attributes tuples. + * @param {StartTag['attributes']} expected Expected attributes tuples. * * @return {boolean} Whether attributes are equivalent. */ @@ -467,8 +507,6 @@ export function isEqualTagAttributePairs( actual, expected ) { /** * Token-type-specific equality handlers - * - * @type {Object} */ export const isEqualTokensOfType = { StartTag: ( actual, expected ) => { @@ -477,9 +515,8 @@ export const isEqualTokensOfType = { return false; } - return isEqualTagAttributePairs( - ...[ actual, expected ].map( getMeaningfulAttributePairs ) - ); + const [ a, e ] = [ actual, expected ].map( getMeaningfulAttributePairs ); + return isEqualTagAttributePairs( a, e ); }, Chars: isEquivalentTextTokens, Comment: isEquivalentTextTokens, @@ -491,11 +528,14 @@ export const isEqualTokensOfType = { * * Mutates the tokens array. * - * @param {Object[]} tokens Set of tokens to search. + * @param {Token[]} tokens Set of tokens to search. * - * @return {Object} Next non-whitespace token. + * @return {Token|void} Next non-whitespace token. */ export function getNextNonWhitespaceToken( tokens ) { + /** + * @type {Token|undefined} + */ let token; while ( ( token = tokens.shift() ) ) { if ( token.type !== 'Chars' ) { @@ -514,7 +554,7 @@ export function getNextNonWhitespaceToken( tokens ) { * * @param {string} html HTML string to tokenize. * - * @return {Object[]|null} Array of valid tokenized HTML elements, or null on error + * @return {Token[]|null} Array of valid tokenized HTML elements, or null on error */ function getHTMLTokens( html ) { try { @@ -529,10 +569,10 @@ function getHTMLTokens( html ) { /** * Returns true if the next HTML token closes the current token. * - * @param {Object} currentToken Current token to compare with. - * @param {Object|undefined} nextToken Next token to compare against. + * @param {Token} currentToken Current token to compare with. + * @param {Token|undefined} nextToken Next token to compare against. * - * @return {boolean} true if `nextToken` closes `currentToken`, false otherwise + * @return {boolean} `true` if `nextToken` closes `currentToken`, `false` otherwise */ export function isClosedByToken( currentToken, nextToken ) { // Ensure this is a self closed token @@ -620,9 +660,9 @@ export function isEquivalentHTML( actual, expected ) { * * Logs to console in development environments when invalid. * - * @param {string|Object} blockTypeOrName Block type. - * @param {Object} attributes Parsed block attributes. - * @param {string} originalBlockContent Original block content. + * @param {string|BlockType} blockTypeOrName Block type. + * @param {Record} attributes Parsed block attributes. + * @param {string} originalBlockContent Original block content. * * @return {boolean} Whether block is valid. */ diff --git a/packages/blocks/src/block-content-provider/index.js b/packages/blocks/src/block-content-provider/index.js index 352550a87be80..27a15ebfbbe18 100644 --- a/packages/blocks/src/block-content-provider/index.js +++ b/packages/blocks/src/block-content-provider/index.js @@ -9,6 +9,19 @@ import { createContext, RawHTML } from '@wordpress/element'; */ import { serialize } from '../api'; +/** + * @typedef {import('@wordpress/blocks').BlockInstance} BlockInstance + */ + +/** + * @template T + * @typedef {import('@wordpress/element').ComponentType} ComponentType + */ + +/** + * @typedef {import('@wordpress/element').ReactNode} ReactNode + */ + const { Consumer, Provider } = createContext( () => {} ); /** @@ -19,14 +32,17 @@ const { Consumer, Provider } = createContext( () => {} ); * `InnerBlocks.Content` component to render block content. * * @example - * * ```jsx * * { blockSaveElement } * * ``` * - * @return {WPElement} Element with BlockContent injected via context. + * @param {Object} props + * @param {ReactNode} props.children + * @param {BlockInstance[]} props.innerBlocks + * + * @return {JSX.Element} Element with BlockContent injected via context. */ const BlockContentProvider = ( { children, innerBlocks } ) => { const BlockContent = () => { @@ -48,7 +64,9 @@ const BlockContentProvider = ( { children, innerBlocks } ) => { * A Higher Order Component used to inject BlockContent using context to the * wrapped component. * - * @return {Component} Enhanced component with injected BlockContent as prop. + * @param {ComponentType} OriginalComponent + * + * @return {ComponentType} Enhanced component with injected BlockContent as prop. */ export const withBlockContentContext = createHigherOrderComponent( ( OriginalComponent ) => { return ( props ) => ( diff --git a/packages/blocks/src/store/actions.js b/packages/blocks/src/store/actions.js index 220ae9be0ea0d..f80b399f5014e 100644 --- a/packages/blocks/src/store/actions.js +++ b/packages/blocks/src/store/actions.js @@ -3,12 +3,28 @@ */ import { castArray } from 'lodash'; +/** + * @typedef {import('redux').AnyAction} Action + */ + +/** + * @typedef {import('@wordpress/blocks').BlockConfiguration} BlockConfiguration + */ + +/** + * @typedef {import('@wordpress/blocks').BlockStyle} BlockStyle + */ + +/** + * @typedef {import('@wordpress/blocks').Category} Category + */ + /** * Returns an action object used in signalling that block types have been added. * - * @param {Array|Object} blockTypes Block types received. + * @param {BlockConfiguration|BlockConfiguration[]} blockTypes Block types received. * - * @return {Object} Action object. + * @return {Action} Action object. */ export function addBlockTypes( blockTypes ) { return { @@ -20,9 +36,9 @@ export function addBlockTypes( blockTypes ) { /** * Returns an action object used to remove a registered block type. * - * @param {string|Array} names Block name. + * @param {string|string[]} names Block name. * - * @return {Object} Action object. + * @return {Action} Action object. */ export function removeBlockTypes( names ) { return { @@ -34,10 +50,10 @@ export function removeBlockTypes( names ) { /** * Returns an action object used in signalling that new block styles have been added. * - * @param {string} blockName Block name. - * @param {Array|Object} styles Block styles. + * @param {string} blockName Block name. + * @param {BlockStyle|BlockStyle[]} styles Block styles. * - * @return {Object} Action object. + * @return {Action} Action object. */ export function addBlockStyles( blockName, styles ) { return { @@ -50,10 +66,10 @@ export function addBlockStyles( blockName, styles ) { /** * Returns an action object used in signalling that block styles have been removed. * - * @param {string} blockName Block name. - * @param {Array|string} styleNames Block style names. + * @param {string} blockName Block name. + * @param {string|string[]} styleNames Block style names. * - * @return {Object} Action object. + * @return {Action} Action object. */ export function removeBlockStyles( blockName, styleNames ) { return { @@ -68,7 +84,7 @@ export function removeBlockStyles( blockName, styleNames ) { * * @param {string} name Block name. * - * @return {Object} Action object. + * @return {Action} Action object. */ export function setDefaultBlockName( name ) { return { @@ -83,7 +99,7 @@ export function setDefaultBlockName( name ) { * * @param {string} name Block name. * - * @return {Object} Action object. + * @return {Action} Action object. */ export function setFreeformFallbackBlockName( name ) { return { @@ -98,7 +114,7 @@ export function setFreeformFallbackBlockName( name ) { * * @param {string} name Block name. * - * @return {Object} Action object. + * @return {Action} Action object. */ export function setUnregisteredFallbackBlockName( name ) { return { @@ -114,7 +130,7 @@ export function setUnregisteredFallbackBlockName( name ) { * * @param {string} name Block name. * - * @return {Object} Action object. + * @return {Action} Action object. */ export function setGroupingBlockName( name ) { return { @@ -126,9 +142,9 @@ export function setGroupingBlockName( name ) { /** * Returns an action object used to set block categories. * - * @param {Object[]} categories Block categories. + * @param {Category[]} categories Block categories. * - * @return {Object} Action object. + * @return {Action} Action object. */ export function setCategories( categories ) { return { @@ -140,10 +156,10 @@ export function setCategories( categories ) { /** * Returns an action object used to update a category. * - * @param {string} slug Block category slug. - * @param {Object} category Object containing the category properties that should be updated. + * @param {string} slug Block category slug. + * @param {Partial} category Object containing the category properties that should be updated. * - * @return {Object} Action object. + * @return {Action} Action object. */ export function updateCategory( slug, category ) { return { diff --git a/packages/blocks/src/store/reducer.js b/packages/blocks/src/store/reducer.js index 4e3131ca7b62e..afc9c16c02c56 100644 --- a/packages/blocks/src/store/reducer.js +++ b/packages/blocks/src/store/reducer.js @@ -19,9 +19,43 @@ import { import { combineReducers } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; +/** + * @typedef {import('@wordpress/blocks').Block>} BlockType + */ + +/** + * @typedef {import('@wordpress/blocks').BlockStyle} BlockStyle + */ + +/** + * @typedef {import('@wordpress/blocks').Category} Category + */ + +/** + * @typedef {import('redux').AnyAction} Action + */ + +/** + * @template S + * @template {Action} A + * @typedef {import('redux').Reducer} Reducer + */ + +/** + * @typedef {Object} State + * @prop {Record} blockTypes + * @prop {Record} blockStyles + * @prop {Category[]} categories + * @prop {string|undefined} defaultBlockName + * @prop {string|undefined} freeformFallbackBlockName + * @prop {string|undefined} groupingBlockName + * @prop {string|undefined} unregisteredFallbackBlockName + */ + /** * Module Constants */ +/** @type {Category[]} */ export const DEFAULT_CATEGORIES = [ { slug: 'common', title: __( 'Common Blocks' ) }, { slug: 'formatting', title: __( 'Formatting' ) }, @@ -31,24 +65,27 @@ export const DEFAULT_CATEGORIES = [ { slug: 'reusable', title: __( 'Reusable Blocks' ) }, ]; +// eslint-disable-next-line valid-jsdoc /** * Reducer managing the block types * - * @param {Object} state Current state. - * @param {Object} action Dispatched action. + * @param {State['blockTypes']} state Current state. + * @param {Action} action Dispatched action. * - * @return {Object} Updated state. + * @return {State['blockTypes']} Updated state. */ export function blockTypes( state = {}, action ) { switch ( action.type ) { case 'ADD_BLOCK_TYPES': - return { - ...state, - ...keyBy( - map( action.blockTypes, ( blockType ) => omit( blockType, 'styles ' ) ), - 'name' - ), - }; + return /** @type {State['blockTypes']} */ ( + { + ...state, + ...keyBy( + map( action.blockTypes, ( blockType ) => omit( blockType, 'styles ' ) ), + 'name' + ), + } + ); case 'REMOVE_BLOCK_TYPES': return omit( state, action.names ); } @@ -56,13 +93,14 @@ export function blockTypes( state = {}, action ) { return state; } +// eslint-disable-next-line valid-jsdoc /** * Reducer managing the block style variations. * - * @param {Object} state Current state. - * @param {Object} action Dispatched action. + * @param {State['blockStyles']} state Current state. + * @param {Action} action Dispatched action. * - * @return {Object} Updated state. + * @return {State['blockStyles']} Updated state. */ export function blockStyles( state = {}, action ) { switch ( action.type ) { @@ -102,7 +140,7 @@ export function blockStyles( state = {}, action ) { * * @param {string} setActionType Action type. * - * @return {function} Reducer. + * @return {Reducer} Reducer. */ export function createBlockNameSetterReducer( setActionType ) { return ( state = null, action ) => { @@ -126,13 +164,14 @@ export const freeformFallbackBlockName = createBlockNameSetterReducer( 'SET_FREE export const unregisteredFallbackBlockName = createBlockNameSetterReducer( 'SET_UNREGISTERED_FALLBACK_BLOCK_NAME' ); export const groupingBlockName = createBlockNameSetterReducer( 'SET_GROUPING_BLOCK_NAME' ); +// eslint-disable-next-line valid-jsdoc /** * Reducer managing the categories * - * @param {Object} state Current state. - * @param {Object} action Dispatched action. + * @param {State['categories']} state Current state. + * @param {Action} action Dispatched action. * - * @return {Object} Updated state. + * @return {State['categories']} Updated state. */ export function categories( state = DEFAULT_CATEGORIES, action ) { switch ( action.type ) { diff --git a/packages/blocks/src/store/selectors.js b/packages/blocks/src/store/selectors.js index cb9333e3f6ff1..891e56fa9fc1b 100644 --- a/packages/blocks/src/store/selectors.js +++ b/packages/blocks/src/store/selectors.js @@ -4,14 +4,34 @@ import createSelector from 'rememo'; import { filter, get, includes, map, some, flow, deburr } from 'lodash'; +/** + * @typedef {import('@wordpress/blocks').Block>} BlockType + */ + +/** + * @typedef {import('@wordpress/blocks').BlockStyle} BlockStyle + */ + +/** + * @typedef {import('@wordpress/blocks').BlockSupports} BlockSupports + */ + +/** + * @typedef {import('@wordpress/blocks').Category} Category + */ + +/** + * @typedef {import('./reducer').State} State + */ + /** * Given a block name or block type object, returns the corresponding * normalized block type object. * - * @param {Object} state Blocks state. - * @param {(string|Object)} nameOrType Block name or type object + * @param {State} state Blocks state. + * @param {string|BlockType} nameOrType Block name or type object * - * @return {Object} Block type object. + * @return {BlockType} Block type object. */ const getNormalizedBlockType = ( state, nameOrType ) => ( 'string' === typeof nameOrType ? @@ -22,9 +42,9 @@ const getNormalizedBlockType = ( state, nameOrType ) => ( /** * Returns all the available block types. * - * @param {Object} state Data state. + * @param {State} state Data state. * - * @return {Array} Block Types. + * @return {Block[]} Block Types. */ export const getBlockTypes = createSelector( ( state ) => Object.values( state.blockTypes ), @@ -36,10 +56,10 @@ export const getBlockTypes = createSelector( /** * Returns a block type by name. * - * @param {Object} state Data state. + * @param {State} state Data state. * @param {string} name Block type name. * - * @return {Object?} Block Type. + * @return {BlockType|undefined} Block Type. */ export function getBlockType( state, name ) { return state.blockTypes[ name ]; @@ -48,10 +68,10 @@ export function getBlockType( state, name ) { /** * Returns block styles by block name. * - * @param {Object} state Data state. + * @param {State} state Data state. * @param {string} name Block type name. * - * @return {Array?} Block Styles. + * @return {BlockStyle[]|undefined} Block Styles. */ export function getBlockStyles( state, name ) { return state.blockStyles[ name ]; @@ -60,9 +80,9 @@ export function getBlockStyles( state, name ) { /** * Returns all the available categories. * - * @param {Object} state Data state. + * @param {State} state Data state. * - * @return {Array} Categories list. + * @return {Category[]} Categories list. */ export function getCategories( state ) { return state.categories; @@ -71,9 +91,9 @@ export function getCategories( state ) { /** * Returns the name of the default block name. * - * @param {Object} state Data state. + * @param {State} state Data state. * - * @return {string?} Default block name. + * @return {string|undefined} Default block name. */ export function getDefaultBlockName( state ) { return state.defaultBlockName; @@ -82,9 +102,9 @@ export function getDefaultBlockName( state ) { /** * Returns the name of the block for handling non-block content. * - * @param {Object} state Data state. + * @param {State} state Data state. * - * @return {string?} Name of the block for handling non-block content. + * @return {string|undefined} Name of the block for handling non-block content. */ export function getFreeformFallbackBlockName( state ) { return state.freeformFallbackBlockName; @@ -93,9 +113,9 @@ export function getFreeformFallbackBlockName( state ) { /** * Returns the name of the block for handling unregistered blocks. * - * @param {Object} state Data state. + * @param {State} state Data state. * - * @return {string?} Name of the block for handling unregistered blocks. + * @return {string|undefined} Name of the block for handling unregistered blocks. */ export function getUnregisteredFallbackBlockName( state ) { return state.unregisteredFallbackBlockName; @@ -104,9 +124,9 @@ export function getUnregisteredFallbackBlockName( state ) { /** * Returns the name of the block for handling unregistered blocks. * - * @param {Object} state Data state. + * @param {State} state Data state. * - * @return {string?} Name of the block for handling unregistered blocks. + * @return {string|undefined} Name of the block for handling unregistered blocks. */ export function getGroupingBlockName( state ) { return state.groupingBlockName; @@ -115,10 +135,10 @@ export function getGroupingBlockName( state ) { /** * Returns an array with the child blocks of a given block. * - * @param {Object} state Data state. + * @param {State} state Data state. * @param {string} blockName Block type name. * - * @return {Array} Array of child block names. + * @return {string[]} Array of child block names. */ export const getChildBlockNames = createSelector( ( state, blockName ) => { @@ -134,16 +154,16 @@ export const getChildBlockNames = createSelector( ] ); +// eslint-disable-next-line valid-jsdoc /** * Returns the block support value for a feature, if defined. * - * @param {Object} state Data state. - * @param {(string|Object)} nameOrType Block name or type object - * @param {string} feature Feature to retrieve - * @param {*} defaultSupports Default value to return if not - * explicitly defined + * @param {State} state Data state. + * @param {string|BlockType} nameOrType Block name or type object + * @param {keyof BlockSupports} feature Feature to retrieve + * @param {any} [defaultSupports] Default value to return if not explicitly defined * - * @return {?*} Block support value + * @return {any} Block support value */ export const getBlockSupport = ( state, nameOrType, feature, defaultSupports ) => { const blockType = getNormalizedBlockType( state, nameOrType ); @@ -154,14 +174,14 @@ export const getBlockSupport = ( state, nameOrType, feature, defaultSupports ) = ], defaultSupports ); }; +// eslint-disable-next-line valid-jsdoc /** * Returns true if the block defines support for a feature, or false otherwise. * - * @param {Object} state Data state. - * @param {(string|Object)} nameOrType Block name or type object. - * @param {string} feature Feature to test. - * @param {boolean} defaultSupports Whether feature is supported by - * default if not explicitly defined. + * @param {State} state Data state. + * @param {string|BlockType} nameOrType Block name or type object. + * @param {keyof BlockSupports} feature Feature to test. + * @param {boolean} [defaultSupports] Whether feature is supported by default if not explicitly defined. * * @return {boolean} Whether block supports feature. */ @@ -173,11 +193,11 @@ export function hasBlockSupport( state, nameOrType, feature, defaultSupports ) { * Returns true if the block type by the given name or object value matches a * search term, or false otherwise. * - * @param {Object} state Blocks state. - * @param {(string|Object)} nameOrType Block name or type object. - * @param {string} searchTerm Search term by which to filter. + * @param {State} state Blocks state. + * @param {string|BlockType} nameOrType Block name or type object. + * @param {string} searchTerm Search term by which to filter. * - * @return {Object[]} Whether block type matches search term. + * @return {boolean} Whether block type matches search term. */ export function isMatchingSearchTerm( state, nameOrType, searchTerm ) { const blockType = getNormalizedBlockType( state, nameOrType ); @@ -216,10 +236,10 @@ export function isMatchingSearchTerm( state, nameOrType, searchTerm ) { /** * Returns a boolean indicating if a block has child blocks or not. * - * @param {Object} state Data state. + * @param {State} state Data state. * @param {string} blockName Block type name. * - * @return {boolean} True if a block contains child blocks and false otherwise. + * @return {boolean} `true` if a block contains child blocks and `false` otherwise. */ export const hasChildBlocks = ( state, blockName ) => { return getChildBlockNames( state, blockName ).length > 0; @@ -228,11 +248,11 @@ export const hasChildBlocks = ( state, blockName ) => { /** * Returns a boolean indicating if a block has at least one child block with inserter support. * - * @param {Object} state Data state. + * @param {State} state Data state. * @param {string} blockName Block type name. * - * @return {boolean} True if a block contains at least one child blocks with inserter support - * and false otherwise. + * @return {boolean} `true` if a block contains at least one child blocks with + * inserter support and `false` otherwise. */ export const hasChildBlocksWithInserterSupport = ( state, blockName ) => { return some( getChildBlockNames( state, blockName ), ( childBlockName ) => { diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000000000..7e33be247ad08 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "allowJs": true, + "allowSyntheticDefaultImports": true, + "checkJs": true, + "jsx": "preserve", + "lib": ["dom", "esnext"], + "module": "commonjs", + "noEmit": true, + "resolveJsonModule": true, + "target": "esnext", + + /* Strict Type-Checking Options */ + "strict": true, /* Enable all strict type-checking options. */ + "noImplicitAny": false, /* Raise error on expressions and declarations with an implied 'any' type. */ + + /* Additional Checks */ + "noUnusedLocals": true, /* Report errors on unused locals. */ + "noUnusedParameters": true, /* Report errors on unused parameters. */ + "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + + }, + "include": [ + "./packages/blocks/**/*.js" + ], + "exclude": [ + "./packages/**/test/**" + ] +}