From 8be7a8d0ee338c9d4668dab08733266a325f713f Mon Sep 17 00:00:00 2001 From: sarayourfriend Date: Tue, 6 Apr 2021 05:49:45 -0700 Subject: [PATCH] dom: Add types to clean-node-list (#30412) * dom: Type dependencies of clean-node-list * dom: Add types to clean-node-list * Fix property descriptions --- packages/dom/README.md | 6 ++--- packages/dom/src/dom/clean-node-list.js | 34 ++++++++++++++++++++----- packages/dom/src/dom/insert-after.js | 10 ++++++-- packages/dom/src/dom/is-element.js | 9 +++++++ packages/dom/src/dom/is-empty.js | 6 +++-- packages/dom/src/dom/remove.js | 8 +++++- packages/dom/src/dom/unwrap.js | 7 +++++ packages/dom/tsconfig.json | 6 +++++ 8 files changed, 71 insertions(+), 15 deletions(-) create mode 100644 packages/dom/src/dom/is-element.js diff --git a/packages/dom/README.md b/packages/dom/README.md index 75bfd1e2e2ba20..d3a80a51ac699f 100644 --- a/packages/dom/README.md +++ b/packages/dom/README.md @@ -151,8 +151,8 @@ the latter. _Parameters_ -- _newNode_ `Element`: Node to be inserted. -- _referenceNode_ `Element`: Node after which to perform the insertion. +- _newNode_ `Node`: Node to be inserted. +- _referenceNode_ `Node`: Node after which to perform the insertion. _Returns_ @@ -291,7 +291,7 @@ Given a DOM node, removes it from the DOM. _Parameters_ -- _node_ `Element`: Node to be removed. +- _node_ `Node`: Node to be removed. _Returns_ diff --git a/packages/dom/src/dom/clean-node-list.js b/packages/dom/src/dom/clean-node-list.js index 45bea2becdffd0..0ac9f7dd622712 100644 --- a/packages/dom/src/dom/clean-node-list.js +++ b/packages/dom/src/dom/clean-node-list.js @@ -11,6 +11,21 @@ import remove from './remove'; import unwrap from './unwrap'; import { isPhrasingContent } from '../phrasing-content'; import insertAfter from './insert-after'; +import isElement from './is-element'; + +/* eslint-disable jsdoc/valid-types */ +/** + * @typedef SchemaItem + * @property {string[]} [attributes] Attributes. + * @property {(string | RegExp)[]} [classes] Classnames or RegExp to test against. + * @property {'*' | { [tag: string]: SchemaItem }} [children] Child schemas. + * @property {string[]} [require] Selectors to test required children against. Leave empty or undefined if there are no requirements. + * @property {boolean} allowEmpty Whether to allow nodes without children. + * @property {(node: Node) => boolean} [isMatch] Function to test whether a node is a match. If left undefined any node will be assumed to match. + */ + +/** @typedef {{ [tag: string]: SchemaItem }} Schema */ +/* eslint-enable jsdoc/valid-types */ /** * Given a schema, unwraps or removes nodes, attributes and classes on a node @@ -18,20 +33,22 @@ import insertAfter from './insert-after'; * * @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 {Schema} schema An array of functions that can mutate with the provided node. + * @param {boolean} inline Whether to clean for inline mode. */ export default function cleanNodeList( nodeList, doc, schema, inline ) { - Array.from( nodeList ).forEach( ( node ) => { + Array.from( nodeList ).forEach( ( + /** @type {Node & { nextElementSibling?: unknown }} */ node + ) => { const tag = node.nodeName.toLowerCase(); // It's a valid child, if the tag exists in the schema without an isMatch // function, or with an isMatch function that matches the node. if ( schema.hasOwnProperty( tag ) && - ( ! schema[ tag ].isMatch || schema[ tag ].isMatch( node ) ) + ( ! schema[ tag ].isMatch || schema[ tag ].isMatch?.( node ) ) ) { - if ( node.nodeType === node.ELEMENT_NODE ) { + if ( isElement( node ) ) { const { attributes = [], classes = [], @@ -64,9 +81,11 @@ export default function cleanNodeList( nodeList, doc, schema, inline ) { if ( node.classList && node.classList.length ) { const mattchers = classes.map( ( item ) => { if ( typeof item === 'string' ) { - return ( className ) => className === item; + return ( /** @type {string} */ className ) => + className === item; } else if ( item instanceof RegExp ) { - return ( className ) => item.test( className ); + return ( /** @type {string} */ className ) => + item.test( className ); } return noop; @@ -113,6 +132,7 @@ export default function cleanNodeList( nodeList, doc, schema, inline ) { // contains children that are block content, unwrap // the node because it is invalid. } else if ( + node.parentNode && node.parentNode.nodeName === 'BODY' && isPhrasingContent( node ) ) { diff --git a/packages/dom/src/dom/insert-after.js b/packages/dom/src/dom/insert-after.js index c81cd0276c3f1a..6ae8b9a8744a96 100644 --- a/packages/dom/src/dom/insert-after.js +++ b/packages/dom/src/dom/insert-after.js @@ -1,11 +1,17 @@ +/** + * Internal dependencies + */ +import { assertIsDefined } from '../utils/assert-is-defined'; + /** * Given two DOM nodes, inserts the former in the DOM as the next sibling of * the latter. * - * @param {Element} newNode Node to be inserted. - * @param {Element} referenceNode Node after which to perform the insertion. + * @param {Node} newNode Node to be inserted. + * @param {Node} referenceNode Node after which to perform the insertion. * @return {void} */ export default function insertAfter( newNode, referenceNode ) { + assertIsDefined( referenceNode.parentNode, 'referenceNode.parentNode' ); referenceNode.parentNode.insertBefore( newNode, referenceNode.nextSibling ); } diff --git a/packages/dom/src/dom/is-element.js b/packages/dom/src/dom/is-element.js new file mode 100644 index 00000000000000..e90a88173a3380 --- /dev/null +++ b/packages/dom/src/dom/is-element.js @@ -0,0 +1,9 @@ +/* eslint-disable jsdoc/valid-types */ +/** + * @param {Node | null | undefined} node + * @return {node is Element} True if node is an Element node + */ +export default function isElement( node ) { + /* eslint-enable jsdoc/valid-types */ + return !! node && node.nodeType === node.ELEMENT_NODE; +} diff --git a/packages/dom/src/dom/is-empty.js b/packages/dom/src/dom/is-empty.js index cffe10ec9ad0cd..b524acb1a54c74 100644 --- a/packages/dom/src/dom/is-empty.js +++ b/packages/dom/src/dom/is-empty.js @@ -11,7 +11,7 @@ export default function isEmpty( element ) { case element.TEXT_NODE: // We cannot use \s since it includes special spaces which we want // to preserve. - return /^[ \f\n\r\t\v\u00a0]*$/.test( element.nodeValue ); + return /^[ \f\n\r\t\v\u00a0]*$/.test( element.nodeValue || '' ); case element.ELEMENT_NODE: if ( element.hasAttributes() ) { return false; @@ -19,7 +19,9 @@ export default function isEmpty( element ) { return true; } - return Array.from( element.childNodes ).every( isEmpty ); + return /** @type {Element[]} */ ( Array.from( + element.childNodes + ) ).every( isEmpty ); default: return true; } diff --git a/packages/dom/src/dom/remove.js b/packages/dom/src/dom/remove.js index 2e22108a08045e..0cec141fa03957 100644 --- a/packages/dom/src/dom/remove.js +++ b/packages/dom/src/dom/remove.js @@ -1,9 +1,15 @@ +/** + * Internal dependencies + */ +import { assertIsDefined } from '../utils/assert-is-defined'; + /** * Given a DOM node, removes it from the DOM. * - * @param {Element} node Node to be removed. + * @param {Node} node Node to be removed. * @return {void} */ export default function remove( node ) { + assertIsDefined( node.parentNode, 'node.parentNode' ); node.parentNode.removeChild( node ); } diff --git a/packages/dom/src/dom/unwrap.js b/packages/dom/src/dom/unwrap.js index 6a93a40625477c..9f58f2d801cac3 100644 --- a/packages/dom/src/dom/unwrap.js +++ b/packages/dom/src/dom/unwrap.js @@ -1,3 +1,8 @@ +/** + * Internal dependencies + */ +import { assertIsDefined } from '../utils/assert-is-defined'; + /** * Unwrap the given node. This means any child nodes are moved to the parent. * @@ -8,6 +13,8 @@ export default function unwrap( node ) { const parent = node.parentNode; + assertIsDefined( parent, 'node.parentNode' ); + while ( node.firstChild ) { parent.insertBefore( node.firstChild, node ); } diff --git a/packages/dom/tsconfig.json b/packages/dom/tsconfig.json index bb25bca0da0eb1..e03e5e0af0eaa8 100644 --- a/packages/dom/tsconfig.json +++ b/packages/dom/tsconfig.json @@ -7,8 +7,14 @@ "include": [ "src/data-transfer.js", "src/dom/caret-range-from-point.js", + "src/dom/clean-node-list.js", "src/dom/compute-caret-rect.js", "src/dom/get-rectangle-from-range.js", + "src/dom/is-empty.js", + "src/dom/is-element.js", + "src/dom/insert-after.js", + "src/dom/remove.js", + "src/dom/unwrap.js", "src/utils/**/*", "src/focusable.js", "src/phrasing-content.js",