diff --git a/package-lock.json b/package-lock.json index bdf963e8f412e..4df9fa4af3e85 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10139,6 +10139,15 @@ "@types/react": "*" } }, + "@types/react-dom": { + "version": "16.9.5", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.5.tgz", + "integrity": "sha512-BX6RQ8s9D+2/gDhxrj8OW+YD4R+8hj7FEM/OJHGNR0KipE1h1mSsf39YeyC81qafkq+N3rU3h3RFbLSwE5VqUg==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/react-syntax-highlighter": { "version": "11.0.2", "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-11.0.2.tgz", diff --git a/package.json b/package.json index 9f9c0443ef296..1430c67a12d54 100644 --- a/package.json +++ b/package.json @@ -91,6 +91,7 @@ "@types/lodash": "4.14.149", "@types/prettier": "1.19.0", "@types/qs": "6.9.1", + "@types/react-dom": "16.9.5", "@types/requestidlecallback": "0.3.1", "@types/sprintf-js": "1.1.2", "@wordpress/babel-plugin-import-jsx-pragma": "file:packages/babel-plugin-import-jsx-pragma", diff --git a/packages/element/CHANGELOG.md b/packages/element/CHANGELOG.md index 6fa9e71bbb959..2db200bde4fb9 100644 --- a/packages/element/CHANGELOG.md +++ b/packages/element/CHANGELOG.md @@ -1,5 +1,6 @@ ## Master +- Include TypeScript type declarations ([#21248](https://github.com/WordPress/gutenberg/pull/21248)) - Graduated `__experimentalCreateInterpolateElement` function to stable api: `createInterpolateElement` (see [20699](https://github.com/WordPress/gutenberg/pull/20699)) ## 2.10.0 (2019-12-19) diff --git a/packages/element/README.md b/packages/element/README.md index 1a51dea4046de..c1f819f6116f5 100755 --- a/packages/element/README.md +++ b/packages/element/README.md @@ -180,7 +180,7 @@ _Related_ _Parameters_ -- _child_ `WPElement`: Any renderable child, such as an element, string, or fragment. +- _child_ (unknown type): Any renderable child, such as an element, string, or fragment. - _container_ `HTMLElement`: DOM node into which element should be rendered. # **createRef** @@ -199,7 +199,7 @@ Finds the dom node of a React component. _Parameters_ -- _component_ `WPComponent`: Component's instance. +- _component_ (unknown type): Component's instance. # **forwardRef** @@ -289,13 +289,11 @@ aside from `children` are passed. _Parameters_ -- _props_ `Object`: -- _props.children_ `string`: HTML to render. -- _props.props_ `Object`: Any additonal props to be set on the containing div. +- _props_ `RawHTMLProps`: Children should be a string of HTML. Other props will be passed through to div wrapper. _Returns_ -- `WPComponent`: Dangerously-rendering component. +- `JSX.Element`: Dangerously-rendering component. # **render** @@ -303,7 +301,7 @@ Renders a given element into the target DOM node. _Parameters_ -- _element_ `WPElement`: Element to render. +- _element_ (unknown type): Element to render. - _target_ `HTMLElement`: DOM node into which element should be rendered. # **renderToString** @@ -312,9 +310,9 @@ Serializes a React element to string. _Parameters_ -- _element_ `WPElement`: Element to serialize. -- _context_ `?Object`: Context object. -- _legacyContext_ `?Object`: Legacy context object. +- _element_ (unknown type): Element to serialize. +- _context_ `[Object]`: Context object. +- _legacyContext_ `[Object]`: Legacy context object. _Returns_ diff --git a/packages/element/package.json b/packages/element/package.json index 0b54d5538af07..28cf5b051987a 100644 --- a/packages/element/package.json +++ b/packages/element/package.json @@ -21,6 +21,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "types": "build-types", "sideEffects": false, "dependencies": { "@babel/runtime": "^7.8.3", diff --git a/packages/element/src/create-interpolate-element.js b/packages/element/src/create-interpolate-element.js index e6d0eefdd3bfa..d3da32354bb11 100644 --- a/packages/element/src/create-interpolate-element.js +++ b/packages/element/src/create-interpolate-element.js @@ -3,6 +3,8 @@ */ import { createElement, cloneElement, Fragment, isValidElement } from 'react'; +/** @typedef {import('./react').WPElement} WPElement */ + let indoc, offset, output, stack; /** @@ -22,6 +24,24 @@ let indoc, offset, output, stack; */ const tokenizer = /<(\/)?(\w+)\s*(\/)?>/g; +/** + * The stack frame tracking parse progress. + * + * @typedef Frame + * + * @property {WPElement} element A parent element which may still have + * @property {number} tokenStart Offset at which parent element first + * appears. + * @property {number} tokenLength Length of string marking start of parent + * element. + * @property {number} [prevOffset] Running offset at which parsing should + * continue. + * @property {number} [leadingTextStart] Offset at which last closing element + * finished, used for finding text between + * elements. + * @property {WPElement[]} children Children. + */ + /** * Tracks recursive-descent parse state. * @@ -29,21 +49,21 @@ const tokenizer = /<(\/)?(\w+)\s*(\/)?>/g; * parsed. * * @private - * @param {WPElement} element A parent element which may still have - * nested children not yet parsed. - * @param {number} tokenStart Offset at which parent element first - * appears. - * @param {number} tokenLength Length of string marking start of parent - * element. - * @param {number} prevOffset Running offset at which parsing should - * continue. - * @param {number} leadingTextStart Offset at which last closing element - * finished, used for finding text between - * elements + * @param {WPElement} element A parent element which may still have + * nested children not yet parsed. + * @param {number} tokenStart Offset at which parent element first + * appears. + * @param {number} tokenLength Length of string marking start of parent + * element. + * @param {number} [prevOffset] Running offset at which parsing should + * continue. + * @param {number} [leadingTextStart] Offset at which last closing element + * finished, used for finding text between + * elements. * * @return {Frame} The stack frame tracking parse progress. */ -function Frame( +function createFrame( element, tokenStart, tokenLength, @@ -175,14 +195,14 @@ function proceed( conversionMap ) { // otherwise we found an inner element addChild( - new Frame( conversionMap[ name ], startOffset, tokenLength ) + createFrame( conversionMap[ name ], startOffset, tokenLength ) ); offset = startOffset + tokenLength; return true; case 'opener': stack.push( - new Frame( + createFrame( conversionMap[ name ], startOffset, tokenLength, @@ -210,7 +230,7 @@ function proceed( conversionMap ) { ); stackTop.children.push( text ); stackTop.prevOffset = startOffset + tokenLength; - const frame = new Frame( + const frame = createFrame( stackTop.element, stackTop.tokenStart, stackTop.tokenLength, diff --git a/packages/element/src/raw-html.js b/packages/element/src/raw-html.js index 0f56060e3057b..f75a9ff66e4fc 100644 --- a/packages/element/src/raw-html.js +++ b/packages/element/src/raw-html.js @@ -3,17 +3,21 @@ */ import { createElement } from './react'; +// Disable reason: JSDoc linter doesn't seem to parse the union (`&`) correctly. +/* eslint-disable jsdoc/valid-types */ +/** @typedef {{children: string} & import('react').ComponentPropsWithoutRef<'div'>} RawHTMLProps */ +/* eslint-enable jsdoc/valid-types */ + /** * Component used as equivalent of Fragment with unescaped HTML, in cases where * it is desirable to render dangerous HTML without needing a wrapper element. * To preserve additional props, a `div` wrapper _will_ be created if any props * aside from `children` are passed. * - * @param {Object} props - * @param {string} props.children HTML to render. - * @param {Object} props.props Any additonal props to be set on the containing div. + * @param {RawHTMLProps} props Children should be a string of HTML. Other props + * will be passed through to div wrapper. * - * @return {WPComponent} Dangerously-rendering component. + * @return {JSX.Element} Dangerously-rendering component. */ export default function RawHTML( { children, ...props } ) { // The DIV wrapper will be stripped by serializer, unless there are diff --git a/packages/element/src/react-platform.js b/packages/element/src/react-platform.js index 44a3422ed7977..fe3d3e94a9994 100644 --- a/packages/element/src/react-platform.js +++ b/packages/element/src/react-platform.js @@ -13,7 +13,7 @@ import { * * @see https://github.com/facebook/react/issues/10309#issuecomment-318433235 * - * @param {WPElement} child Any renderable child, such as an element, + * @param {import('./react').WPElement} child Any renderable child, such as an element, * string, or fragment. * @param {HTMLElement} container DOM node into which element should be rendered. */ @@ -22,14 +22,14 @@ export { createPortal }; /** * Finds the dom node of a React component. * - * @param {WPComponent} component Component's instance. + * @param {import('./react').WPComponent} component Component's instance. */ export { findDOMNode }; /** * Renders a given element into the target DOM node. * - * @param {WPElement} element Element to render. + * @param {import('./react').WPElement} element Element to render. * @param {HTMLElement} target DOM node into which element should be rendered. */ export { render }; diff --git a/packages/element/src/react.js b/packages/element/src/react.js index 449b799ef74f8..78752a1b5a179 100644 --- a/packages/element/src/react.js +++ b/packages/element/src/react.js @@ -37,7 +37,7 @@ import { isString } from 'lodash'; /** * Object containing a React component. * - * @typedef {import('react').Component} WPComponent + * @typedef {import('react').ComponentType} WPComponent */ /** diff --git a/packages/element/src/serialize.js b/packages/element/src/serialize.js index d5ef4e02a7758..172657812533e 100644 --- a/packages/element/src/serialize.js +++ b/packages/element/src/serialize.js @@ -52,7 +52,9 @@ import { import { createContext, Fragment, StrictMode, forwardRef } from './react'; import RawHTML from './raw-html'; -const { Provider, Consumer } = createContext(); +/** @typedef {import('./react').WPElement} WPElement */ + +const { Provider, Consumer } = createContext( undefined ); const ForwardRef = forwardRef( () => { return null; } ); @@ -60,14 +62,14 @@ const ForwardRef = forwardRef( () => { /** * Valid attribute types. * - * @type {Set} + * @type {Set} */ const ATTRIBUTES_TYPES = new Set( [ 'string', 'boolean', 'number' ] ); /** * Element tags which can be self-closing. * - * @type {Set} + * @type {Set} */ const SELF_CLOSING_TAGS = new Set( [ 'area', @@ -101,7 +103,7 @@ const SELF_CLOSING_TAGS = new Set( [ * [ tr.firstChild.textContent.trim() ]: true * } ), {} ) ).sort(); * - * @type {Set} + * @type {Set} */ const BOOLEAN_ATTRIBUTES = new Set( [ 'allowfullscreen', @@ -152,7 +154,7 @@ const BOOLEAN_ATTRIBUTES = new Set( [ * * - `alt`: https://blog.whatwg.org/omit-alt * - * @type {Set} + * @type {Set} */ const ENUMERATED_ATTRIBUTES = new Set( [ 'autocapitalize', @@ -195,7 +197,7 @@ const ENUMERATED_ATTRIBUTES = new Set( [ * .map( ( [ key ] ) => key ) * .sort(); * - * @type {Set} + * @type {Set} */ const CSS_PROPERTIES_SUPPORTS_UNITLESS = new Set( [ 'animation', @@ -269,7 +271,7 @@ function isInternalAttribute( attribute ) { * @param {string} attribute Attribute name. * @param {*} value Non-normalized attribute value. * - * @return {string} Normalized attribute value. + * @return {*} Normalized attribute value. */ function getNormalAttributeValue( attribute, value ) { switch ( attribute ) { @@ -346,9 +348,9 @@ function getNormalStylePropertyValue( property, value ) { /** * Serializes a React element to string. * - * @param {WPElement} element Element to serialize. - * @param {?Object} context Context object. - * @param {?Object} legacyContext Legacy context object. + * @param {import('react').ReactNode} element Element to serialize. + * @param {Object} [context] Context object. + * @param {Object} [legacyContext] Legacy context object. * * @return {string} Serialized element. */ @@ -369,7 +371,10 @@ export function renderElement( element, context, legacyContext = {} ) { return element.toString(); } - const { type, props } = element; + const { + type, + props, + } = /** @type {{type?: any, props?: any}} */ ( element ); switch ( type ) { case StrictMode: @@ -434,11 +439,11 @@ export function renderElement( element, context, legacyContext = {} ) { /** * Serializes a native component type to string. * - * @param {?string} type Native component type to serialize, or null if - * rendering as fragment of children content. - * @param {Object} props Props object. - * @param {?Object} context Context object. - * @param {?Object} legacyContext Legacy context object. + * @param {?string} type Native component type to serialize, or null if + * rendering as fragment of children content. + * @param {Object} props Props object. + * @param {Object} [context] Context object. + * @param {Object} [legacyContext] Legacy context object. * * @return {string} Serialized element. */ @@ -478,13 +483,15 @@ export function renderNativeComponent( return '<' + type + attributes + '>' + content + ''; } +/** @typedef {import('./react').WPComponent} WPComponent */ + /** * Serializes a non-native component type to string. * - * @param {Function} Component Component type to serialize. - * @param {Object} props Props object. - * @param {?Object} context Context object. - * @param {?Object} legacyContext Legacy context object. + * @param {WPComponent} Component Component type to serialize. + * @param {Object} props Props object. + * @param {Object} [context] Context object. + * @param {Object} [legacyContext] Legacy context object. * * @return {string} Serialized element */ @@ -494,10 +501,22 @@ export function renderComponent( context, legacyContext = {} ) { - const instance = new Component( props, legacyContext ); + const instance = new /** @type {import('react').ComponentClass} */ ( Component )( + props, + legacyContext + ); - if ( typeof instance.getChildContext === 'function' ) { - Object.assign( legacyContext, instance.getChildContext() ); + if ( + typeof ( + // Ignore reason: Current prettier reformats parens and mangles type assertion + // prettier-ignore + /** @type {{getChildContext?: () => unknown}} */ ( instance ).getChildContext + ) === 'function' + ) { + Object.assign( + legacyContext, + /** @type {{getChildContext?: () => unknown}} */ ( instance ).getChildContext() + ); } const html = renderElement( instance.render(), context, legacyContext ); @@ -508,9 +527,9 @@ export function renderComponent( /** * Serializes an array of children to string. * - * @param {Array} children Children to serialize. - * @param {?Object} context Context object. - * @param {?Object} legacyContext Legacy context object. + * @param {import('react').ReactNodeArray} children Children to serialize. + * @param {Object} [context] Context object. + * @param {Object} [legacyContext] Legacy context object. * * @return {string} Serialized children. */ diff --git a/packages/element/tsconfig.json b/packages/element/tsconfig.json new file mode 100644 index 0000000000000..1f90b9c762ae6 --- /dev/null +++ b/packages/element/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "declarationDir": "build-types", + + "noImplicitAny": false, + "strictNullChecks": false + }, + "references": [ { "path": "../escape-html" } ], + "include": [ "src/**/*" ] +} diff --git a/packages/eslint-plugin/configs/jsdoc.js b/packages/eslint-plugin/configs/jsdoc.js index 8bff4a36b7d32..71a97abda2fd0 100644 --- a/packages/eslint-plugin/configs/jsdoc.js +++ b/packages/eslint-plugin/configs/jsdoc.js @@ -59,6 +59,8 @@ const typescriptUtilityTypes = [ 'Required', 'ReturnType', 'ThisType', + 'unknown', + 'never', ]; module.exports = { diff --git a/tsconfig.json b/tsconfig.json index 7cdae4ac9a188..f2d19a1874a85 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,6 +5,7 @@ { "path": "packages/autop" }, { "path": "packages/blob" }, { "path": "packages/block-editor" }, + { "path": "packages/element" }, { "path": "packages/dom-ready" }, { "path": "packages/escape-html" }, { "path": "packages/html-entities" },