diff --git a/docs/designers-developers/developers/data/data-core-keyboard-shortcuts.md b/docs/designers-developers/developers/data/data-core-keyboard-shortcuts.md index fefa8e189e2150..4d41954e24fc1c 100644 --- a/docs/designers-developers/developers/data/data-core-keyboard-shortcuts.md +++ b/docs/designers-developers/developers/data/data-core-keyboard-shortcuts.md @@ -79,7 +79,7 @@ _Parameters_ - _state_ `Object`: Global state. - _name_ `string`: Shortcut name. -- _representation_ (unknown type): Type of representation (display, raw, ariaLabel). +- _representation_ `keyof FORMATTING_METHODS`: Type of representation (display, raw, ariaLabel). _Returns_ diff --git a/package-lock.json b/package-lock.json index cfa78b4f4fc03f..86694410cb7973 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10066,7 +10066,8 @@ "dev": true, "requires": { "@babel/core": "^7.9.0", - "doctrine": "^2.1.0", + "comment-parser": "0.7.2", + "jsdoctypeparser": "6.1.0", "lodash": "^4.17.15", "mdast-util-inject": "1.1.0", "optionator": "0.8.2", diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index 48f4fad4bdff63..28db8caff2bae3 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -296,7 +296,7 @@ _Returns_ # **getFontSize** Returns the font size object based on an array of named font sizes and the namedFontSize and customFontSize values. - If namedFontSize is undefined or not found in fontSizes an object with just the size value based on customFontSize is returned. +If namedFontSize is undefined or not found in fontSizes an object with just the size value based on customFontSize is returned. _Parameters_ diff --git a/packages/docgen/package.json b/packages/docgen/package.json index 8ac42fb05336ac..3c419824cb5d90 100644 --- a/packages/docgen/package.json +++ b/packages/docgen/package.json @@ -23,7 +23,8 @@ }, "dependencies": { "@babel/core": "^7.9.0", - "doctrine": "^2.1.0", + "comment-parser": "0.7.2", + "jsdoctypeparser": "6.1.0", "lodash": "^4.17.15", "mdast-util-inject": "1.1.0", "optionator": "0.8.2", diff --git a/packages/docgen/src/get-jsdoc-from-token.js b/packages/docgen/src/get-jsdoc-from-token.js index e9cbc361c3bc52..8a52c7bc31fc5f 100644 --- a/packages/docgen/src/get-jsdoc-from-token.js +++ b/packages/docgen/src/get-jsdoc-from-token.js @@ -1,7 +1,7 @@ /** * External dependencies. */ -const doctrine = require( 'doctrine' ); +const parse = require( 'comment-parser' ); /** * Internal dependencies. @@ -19,19 +19,138 @@ const getTypeAsString = require( './get-type-as-string' ); */ module.exports = function( token ) { let jsdoc; - const comments = getLeadingComments( token ); + let comments = getLeadingComments( token ); if ( comments && /^\*\r?\n/.test( comments ) ) { - jsdoc = doctrine.parse( comments, { - unwrap: true, - recoverable: true, - sloppy: true, - } ); - jsdoc.tags = jsdoc.tags.map( ( tag ) => { - if ( tag.type ) { - tag.type = getTypeAsString( tag.type ); + comments = encodeWhitespacesInCode( comments ); + + // babel strips /* and */, but comment-parser requires it. + jsdoc = parse( `/*${ comments }\n*/` )[ 0 ]; + + jsdoc.description = decodeWhitespacesInCode( jsdoc.description ); + + delete jsdoc.line; + delete jsdoc.source; + + jsdoc.tags = jsdoc.tags.map( + ( { tag: title, name, type, description, optional } ) => { + const mergeNameAndDesc = () => + `${ name } ${ description }`.trim(); + + if ( title === 'deprecated' ) { + return { + title, + description: mergeNameAndDesc(), + }; + } + + if ( title === 'see' ) { + return { + title, + description: + name.match( /https?:\/\// ) !== null + ? `${ name }\n\n${ description }`.trim() + : mergeNameAndDesc(), + }; + } + + if ( title === 'since' ) { + return { + title, + version: name, + description, + }; + } + + if ( title === 'param' ) { + return { + title, + name, + description, + type: getTypeAsString( type, optional ), + }; + } + + if ( title === 'return' ) { + return { + title, + description: mergeNameAndDesc(), + type: getTypeAsString( type, optional ), + }; + } + + if ( title === 'type' ) { + return { + title, + description: mergeNameAndDesc(), + type: getTypeAsString( type, optional ), + }; + } + + if ( title === 'example' ) { + if ( name.match( /```.*/ ) !== null ) { + description = `${ name }\n${ description }`.trim(); + } else if ( name.match( /.*/ ) !== null ) { + // do nothing + } else { + description = mergeNameAndDesc(); + } + + return { + title, + description: decodeWhitespacesInCode( description ), + }; + } + + if ( title === 'typedef' ) { + return { + title, + name, + type: getTypeAsString( type, optional ), + description, + }; + } + + if ( title === 'property' ) { + return { + title, + name, + type: getTypeAsString( type, optional ), + description, + }; + } + + return { + title, + description, + }; } - return tag; - } ); + ); } return jsdoc; }; + +const codeRegex = /```.*\n[\s\S]*```/g; +const CODE_TAB = '__CODE_TAB__'; +const CODE_SPACE = '__CODE_SPACE__'; + +const encodeWhitespacesInCode = ( comments ) => { + return comments.replace( codeRegex, ( m0 ) => { + return m0.replace( /\n \*[ \t][ \t]+/g, ( m1 ) => { + return ( + '\n * ' + + m1 + .substring( 4 ) + .replace( /\t/g, CODE_TAB ) + .replace( / /g, CODE_SPACE ) + ); + } ); + } ); +}; + +const decodeWhitespacesInCode = ( description ) => { + return description.replace( codeRegex, ( m0 ) => { + return m0 + .replace( new RegExp( CODE_TAB, 'g' ), '\t' ) + .replace( new RegExp( CODE_SPACE, 'g' ), ' ' ); + } ); +}; diff --git a/packages/docgen/src/get-type-as-string.js b/packages/docgen/src/get-type-as-string.js index 6022ded3e90320..1f9b29519f0e18 100644 --- a/packages/docgen/src/get-type-as-string.js +++ b/packages/docgen/src/get-type-as-string.js @@ -1,47 +1,63 @@ -const maybeAddDefault = function( value, defaultValue ) { - if ( defaultValue ) { - return `value=${ defaultValue }`; +const { parse } = require( 'jsdoctypeparser' ); + +const getType = ( ast, typeString, noUnionParenthesis ) => { + if ( ast.type === 'NAME' ) { + return ast.name; + } + + if ( ast.type === 'ANY' ) { + return '*'; + } + + if ( ast.type === 'GENERIC' ) { + const types = ast.objects.map( ( o ) => getType( o ) ).join( ',' ); + return `${ getType( ast.subject ) }<${ types }>`; } - return value; -}; -const getType = function( param, defaultValue ) { - if ( ! defaultValue ) { - defaultValue = param.default; - } - - if ( param.type.type ) { - return getType( param.type, defaultValue ); - } else if ( param.expression ) { - if ( param.type === 'RestType' ) { - return `...${ getType( param.expression, defaultValue ) }`; - } else if ( param.type === 'NullableType' ) { - return `?${ getType( param.expression, defaultValue ) }`; - } else if ( param.type === 'TypeApplication' ) { - return `${ getType( - param.expression, - defaultValue - ) }<${ param.applications - .map( ( application ) => getType( application ) ) - .join( ',' ) }>`; - } else if ( param.type === 'OptionalType' ) { - return `[${ getType( param.expression, defaultValue ) }]`; - } - return getType( param.expression, defaultValue ); - } else if ( param.elements ) { - const types = param.elements.map( ( element ) => getType( element ) ); - return maybeAddDefault( `(${ types.join( '|' ) })`, defaultValue ); - } else if ( param.type === 'AllLiteral' ) { - return maybeAddDefault( '*', defaultValue ); - } else if ( param.type === 'NullLiteral' ) { - return maybeAddDefault( 'null', defaultValue ); - } else if ( param.type === 'UndefinedLiteral' ) { - return maybeAddDefault( 'undefined', defaultValue ); - } - - return maybeAddDefault( param.name, defaultValue ); + if ( ast.type === 'NULLABLE' ) { + return `?${ getType( ast.value ) }`; + } + + if ( ast.type === 'VARIADIC' ) { + return `...${ getType( ast.value ) }`; + } + + if ( ast.type === 'UNION' ) { + const left = getType( ast.left ); + const right = getType( ast.right, null, true ); + const type = `${ left }|${ right }`; + + return noUnionParenthesis ? type : `(${ type })`; + } + + if ( ast.type === 'PARENTHESIS' ) { + const type = getType( ast.value ); + return type[ 0 ] === '(' && type[ type.length - 1 ] === ')' + ? type + : `(${ type })`; + } + + if ( ast.type === 'OPTIONAL' ) { + return `[${ getType( ast.value ) }]`; + } + + if ( ast.type === 'MEMBER' && ast.owner.type === 'IMPORT' ) { + return `${ ast.name }`; + } + + return typeString || 'unknown type'; }; -module.exports = function( param ) { - return getType( param ); +module.exports = function( typeString, optional ) { + let ast; + + try { + ast = parse( typeString ); + } catch { + return 'unknown type'; + } + + const type = getType( ast, typeString ); + + return optional ? `[${ type }]` : type; }; diff --git a/packages/docgen/src/test/fixtures/default-parse-error/exports.json b/packages/docgen/src/test/fixtures/default-parse-error/exports.json index 04e396cc1fc436..6cf22ba030c1a6 100644 --- a/packages/docgen/src/test/fixtures/default-parse-error/exports.json +++ b/packages/docgen/src/test/fixtures/default-parse-error/exports.json @@ -1 +1 @@ -[{"type":"ExportDefaultDeclaration","start":173,"end":288,"loc":{"start":{"line":7,"column":0},"end":{"line":9,"column":1}},"range":[173,288],"declaration":{"type":"FunctionDeclaration","start":188,"end":288,"loc":{"start":{"line":7,"column":15},"end":{"line":9,"column":1}},"range":[188,288],"id":{"type":"Identifier","start":197,"end":221,"loc":{"start":{"line":7,"column":24},"end":{"line":7,"column":48}},"range":[197,221],"name":"invokeCallbackAfterDelay"},"generator":false,"expression":false,"async":false,"params":[{"type":"Identifier","start":223,"end":231,"loc":{"start":{"line":7,"column":50},"end":{"line":7,"column":58}},"range":[223,231],"name":"callback"}],"body":{"type":"BlockStatement","start":234,"end":288,"loc":{"start":{"line":7,"column":61},"end":{"line":9,"column":1}},"range":[234,288],"body":[{"type":"ExpressionStatement","start":237,"end":286,"loc":{"start":{"line":8,"column":1},"end":{"line":8,"column":50}},"range":[237,286],"expression":{"type":"CallExpression","start":237,"end":285,"loc":{"start":{"line":8,"column":1},"end":{"line":8,"column":49}},"range":[237,285],"callee":{"type":"Identifier","start":237,"end":247,"loc":{"start":{"line":8,"column":1},"end":{"line":8,"column":11}},"range":[237,247],"name":"setTimeout"},"arguments":[{"type":"ArrowFunctionExpression","start":249,"end":277,"loc":{"start":{"line":8,"column":13},"end":{"line":8,"column":41}},"range":[249,277],"id":null,"generator":false,"expression":true,"async":false,"params":[],"body":{"type":"CallExpression","start":255,"end":277,"loc":{"start":{"line":8,"column":19},"end":{"line":8,"column":41}},"range":[255,277],"callee":{"type":"Identifier","start":255,"end":263,"loc":{"start":{"line":8,"column":19},"end":{"line":8,"column":27}},"range":[255,263],"name":"callback"},"arguments":[{"type":"CallExpression","start":265,"end":275,"loc":{"start":{"line":8,"column":29},"end":{"line":8,"column":39}},"range":[265,275],"callee":{"type":"MemberExpression","start":265,"end":273,"loc":{"start":{"line":8,"column":29},"end":{"line":8,"column":37}},"range":[265,273],"object":{"type":"Identifier","start":265,"end":269,"loc":{"start":{"line":8,"column":29},"end":{"line":8,"column":33}},"range":[265,269],"name":"Date"},"property":{"type":"Identifier","start":270,"end":273,"loc":{"start":{"line":8,"column":34},"end":{"line":8,"column":37}},"range":[270,273],"name":"now"},"computed":false},"arguments":[]}]}},{"type":"Literal","start":279,"end":283,"loc":{"start":{"line":8,"column":43},"end":{"line":8,"column":47}},"range":[279,283],"value":1000,"raw":"1000"}]}}]}},"leadingComments":[{"type":"Block","value":"*\n * Function invoking callback after delay with current timestamp in milliseconds\n * since epoch.\n *\n * @param {(timestamp:number)=>void} callback Callback function.\n ","start":0,"end":172,"range":[0,172],"loc":{"start":{"line":1,"column":0},"end":{"line":6,"column":3}}}]}] +[{"type":"ExportDefaultDeclaration","start":173,"end":288,"loc":{"start":{"line":7,"column":0},"end":{"line":9,"column":1}},"range":[173,288],"declaration":{"type":"FunctionDeclaration","start":188,"end":288,"loc":{"start":{"line":7,"column":15},"end":{"line":9,"column":1}},"range":[188,288],"id":{"type":"Identifier","start":197,"end":221,"loc":{"start":{"line":7,"column":24},"end":{"line":7,"column":48}},"range":[197,221],"name":"invokeCallbackAfterDelay"},"generator":false,"expression":false,"async":false,"params":[{"type":"Identifier","start":223,"end":231,"loc":{"start":{"line":7,"column":50},"end":{"line":7,"column":58}},"range":[223,231],"name":"callback"}],"body":{"type":"BlockStatement","start":234,"end":288,"loc":{"start":{"line":7,"column":61},"end":{"line":9,"column":1}},"range":[234,288],"body":[{"type":"ExpressionStatement","start":237,"end":286,"loc":{"start":{"line":8,"column":1},"end":{"line":8,"column":50}},"range":[237,286],"expression":{"type":"CallExpression","start":237,"end":285,"loc":{"start":{"line":8,"column":1},"end":{"line":8,"column":49}},"range":[237,285],"callee":{"type":"Identifier","start":237,"end":247,"loc":{"start":{"line":8,"column":1},"end":{"line":8,"column":11}},"range":[237,247],"name":"setTimeout"},"arguments":[{"type":"ArrowFunctionExpression","start":249,"end":277,"loc":{"start":{"line":8,"column":13},"end":{"line":8,"column":41}},"range":[249,277],"id":null,"generator":false,"expression":true,"async":false,"params":[],"body":{"type":"CallExpression","start":255,"end":277,"loc":{"start":{"line":8,"column":19},"end":{"line":8,"column":41}},"range":[255,277],"callee":{"type":"Identifier","start":255,"end":263,"loc":{"start":{"line":8,"column":19},"end":{"line":8,"column":27}},"range":[255,263],"name":"callback"},"arguments":[{"type":"CallExpression","start":265,"end":275,"loc":{"start":{"line":8,"column":29},"end":{"line":8,"column":39}},"range":[265,275],"callee":{"type":"MemberExpression","start":265,"end":273,"loc":{"start":{"line":8,"column":29},"end":{"line":8,"column":37}},"range":[265,273],"object":{"type":"Identifier","start":265,"end":269,"loc":{"start":{"line":8,"column":29},"end":{"line":8,"column":33}},"range":[265,269],"name":"Date"},"property":{"type":"Identifier","start":270,"end":273,"loc":{"start":{"line":8,"column":34},"end":{"line":8,"column":37}},"range":[270,273],"name":"now"},"computed":false},"arguments":[]}]}},{"type":"Literal","start":279,"end":283,"loc":{"start":{"line":8,"column":43},"end":{"line":8,"column":47}},"range":[279,283],"value":1000,"raw":"1000"}]}}]}},"leadingComments":[{"type":"Block","value":"*\n * Function invoking callback after delay with current timestamp in milliseconds\n * since epoch.\n *\n * @param {A&B} callback Callback function.\n ","start":0,"end":172,"range":[0,172],"loc":{"start":{"line":1,"column":0},"end":{"line":6,"column":3}}}]}] diff --git a/packages/docgen/src/test/get-intermediate-representation.js b/packages/docgen/src/test/get-intermediate-representation.js index ab1914dbd397f4..7bd4b23ebbad5c 100644 --- a/packages/docgen/src/test/get-intermediate-representation.js +++ b/packages/docgen/src/test/get-intermediate-representation.js @@ -70,10 +70,9 @@ describe( 'Intermediate Representation', function() { tags: [ { description: 'Callback function.', - errors: [ 'unexpected token' ], name: 'callback', title: 'param', - type: null, + type: 'unknown type', }, ], lineStart: 7, diff --git a/packages/docgen/src/test/get-jsdoc-from-token.js b/packages/docgen/src/test/get-jsdoc-from-token.js index 3475e69938187e..a1b075e3befee4 100644 --- a/packages/docgen/src/test/get-jsdoc-from-token.js +++ b/packages/docgen/src/test/get-jsdoc-from-token.js @@ -3,14 +3,56 @@ */ const getJSDocFromToken = require( '../get-jsdoc-from-token' ); -describe( 'JSDoc', () => { - it( 'extracts description and tags', () => { +const expectToExtractExample = ( code, description ) => { + expect( + getJSDocFromToken( { + leadingComments: [ + { + value: code, + }, + ], + } ) + ).toEqual( { + description: '', + tags: [ + { + title: 'example', + description, + }, + ], + } ); +}; + +describe( 'Parse JSDoc and extract description and tags', () => { + it( 'Normal definition', () => { + const code = ` + * + * A function that adds two parameters. + * + * @deprecated Use native addition instead. + * @since v2 + * + * @see addition + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators + * + * @param {number} firstParam The first param to add. + * @param {number} secondParam The second param to add. + * + * @example + * + * \`\`\`js + * const addResult = sum( 1, 3 ); + * console.log( addResult ); // will yield 4 + * \`\`\` + * + * @return {number} The result of adding the two params. +`.trim(); + expect( getJSDocFromToken( { leadingComments: [ { - value: - '*\n * A function that adds two parameters.\n *\n * @deprecated Use native addition instead.\n * @since v2\n *\n * @see addition\n * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators\n *\n * @param {number} firstParam The first param to add.\n * @param {number} secondParam The second param to add.\n *\n * @example\n *\n * ```js\n * const addResult = sum( 1, 3 );\n * console.log( addResult ); // will yield 4\n * ```\n *\n * @return {number} The result of adding the two params.\n ', + value: code, }, ], } ) @@ -23,14 +65,15 @@ describe( 'JSDoc', () => { }, { title: 'since', - description: 'v2', + version: 'v2', + description: '', }, { title: 'see', description: 'addition', }, { - title: 'link', + title: 'see', description: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators', }, @@ -59,12 +102,42 @@ describe( 'JSDoc', () => { ], } ); + // Test spaces in code are preserved. expect( getJSDocFromToken( { leadingComments: [ { + // Adapted from packages/compose/src/hooks/use-resize-observer/index.js value: - '*\n * Constant to document the meaning of life,\n * the universe and everything else.\n *\n * @type {number}\n ', + '*\n * X\n *\n * @example\n *\n * ```\n * const y = (\n * x === 3\n * );\n * ```\n', + }, + ], + } ) + ).toEqual( { + description: 'X', + tags: [ + { + title: 'example', + description: '```\nconst y = (\n x === 3\n);\n```', + }, + ], + } ); + } ); + + it( 'variable type', () => { + const code = ` + * + * Constant to document the meaning of life, + * the universe and everything else. + * + * @type {number} +`.trim(); + + expect( + getJSDocFromToken( { + leadingComments: [ + { + value: code, }, ], } ) @@ -74,18 +147,25 @@ describe( 'JSDoc', () => { tags: [ { title: 'type', - description: null, + description: '', type: 'number', }, ], } ); + } ); + + it( 'function definition', () => { + const code = ` + * + * Function invoking callback after delay with current timestamp in milliseconds since epoch. + * @param {(timestamp:number)=>void} callback Callback function. +`.trim(); expect( getJSDocFromToken( { leadingComments: [ { - value: - '*\n * Function invoking callback after delay with current timestamp in milliseconds since epoch.\n * @param {(timestamp:number)=>void} callback Callback function.\n ', + value: code, }, ], } ) @@ -95,10 +175,316 @@ describe( 'JSDoc', () => { tags: [ { title: 'param', - errors: [ 'unexpected token' ], description: 'Callback function.', name: 'callback', - type: null, + type: '(timestamp:number)=>void', + }, + ], + } ); + } ); + + it( 'handles parse failure', () => { + const code = ` + * + * WordPress + * @param {A&B} callback It's OK in TypeScript. But it's not in JSDoc. +`.trim(); + + expect( + getJSDocFromToken( { + leadingComments: [ + { + value: code, + }, + ], + } ) + ).toEqual( { + description: 'WordPress', + tags: [ + { + title: 'param', + description: `It's OK in TypeScript. But it's not in JSDoc.`, + name: 'callback', + type: 'unknown type', + }, + ], + } ); + } ); + + it( 'handles code block in description', () => { + // Adapted from rich-text/src/create.js + const code = ` + * + * Blah blah blah + * + * \`\`\`js + * { + * text: string, + * } + * \`\`\` +`.trim(); + + const description = ` +Blah blah blah + +\`\`\`js +{ + text: string, +} +\`\`\` +`.trim(); + + expect( + getJSDocFromToken( { + leadingComments: [ + { + value: code, + }, + ], + } ) + ).toEqual( { + description, + tags: [], + } ); + } ); + + it( 'tabs in code example are preserved', () => { + // Adapted from packages/compose/src/hooks/use-resize-observer/index.js + const code = ` + * + * @example + * + * \`\`\`js + * const App = () => { + * let testTab = ' '; + * const [ resizeListener, sizes ] = useResizeObserver(); + * + * return ( + *
+ * { resizeListener } + * Your content here + *
+ * ); + * }; + * \`\`\` + * +`.trim(); + + const description = ` +\`\`\`js +const App = () => { + let testTab = ' '; + const [ resizeListener, sizes ] = useResizeObserver(); + + return ( +
+ { resizeListener } + Your content here +
+ ); +}; +\`\`\` +`.trim(); + + expectToExtractExample( code, description ); + } ); + + it( 'tabs in code example are preserved even when the first char after * is a tab', () => { + // Adapted from edit-post/src/components/sidebar/plugin-document-setting-panel/index.js + const code = ` + * + * @example + * \`\`\`jsx + * const MyDocumentSettingTest = () => ( + * + *

My Document Setting Panel

+ *
+ * ); + * \`\`\` +`.trim(); + + const description = ` +\`\`\`jsx +const MyDocumentSettingTest = () => ( + +

My Document Setting Panel

+
+); +\`\`\` +`.trim(); + + expectToExtractExample( code, description ); + } ); + + it( 'spaces in code example are preserved', () => { + // Adapted from packages/data/src/components/with-dispatch/index.js + const code = ` + * + * @example + * + * \`\`\` + * const y = ( + * x === 3 + * ); + * \`\`\` +`.trim(); + + const description = ` +\`\`\` +const y = ( + x === 3 +); +\`\`\` +`.trim(); + + expectToExtractExample( code, description ); + } ); + + it( 'no empty line after @example tag: ```js', () => { + // Adapted from a11y/src/index.js + const code = ` + * + * @example + * \`\`\`js + * import { speak } from '@wordpress/a11y'; + * \`\`\` +`.trim(); + + const description = ` +\`\`\`js +import { speak } from '@wordpress/a11y'; +\`\`\` +`.trim(); + + expectToExtractExample( code, description ); + } ); + + it( 'no empty line after @example tag: ```', () => { + // Adapted from a11y/src/index.js + const code = ` + * + * @example + * \`\`\` + * ls -a + * \`\`\` +`.trim(); + + const description = ` +\`\`\` + ls -a +\`\`\` +`.trim(); + + expectToExtractExample( code, description ); + } ); + + it( 'no empty line after @example tag: normal text', () => { + // Adapted from block-serialization-default-parser/src/index.js + const code = ` + * + * @example + * Input post: + * \`\`\`html + * + * \`\`\` + * +`.trim(); + + const description = ` +Input post: +\`\`\`html + +\`\`\` +`.trim(); + + expectToExtractExample( code, description ); + } ); + + it( 'no empty line after @example tag: ', () => { + // Adapted from edit-post/src/components/block-settings-menu/plugin-block-settings-menu-items.js + const code = ` + * + * @example + * ES5 + * \`\`\`js + * // Using ES5 syntax + * var __ = wp.i18n.__; + * \`\`\` +`.trim(); + + const description = ` +\`\`\`js +// Using ES5 syntax +var __ = wp.i18n.__; +\`\`\` +`.trim(); + + expectToExtractExample( code, description ); + } ); + + it( '@see tag with explanation', () => { + const code = ` + * + * @see https://google.com + * + * Find something here. +`.trim(); + + const description = ` +https://google.com + +Find something here. +`.trim(); + + expect( + getJSDocFromToken( { + leadingComments: [ + { + value: code, + }, + ], + } ) + ).toEqual( { + description: '', + tags: [ + { + title: 'see', + description, + }, + ], + } ); + } ); + + it( '@typedef and @property', () => { + // Adapted from block-editor/src/store/selectors.js + const code = ` + * + * @typedef {Object} WPEditorInserterItem + * @property {string} id Unique identifier for the item. +`.trim(); + + expect( + getJSDocFromToken( { + leadingComments: [ + { + value: code, + }, + ], + } ) + ).toEqual( { + description: '', + tags: [ + { + title: 'typedef', + type: 'Object', + name: 'WPEditorInserterItem', + description: '', + }, + { + title: 'property', + name: 'id', + type: 'string', + description: 'Unique identifier for the item.', }, ], } ); diff --git a/packages/docgen/src/test/get-type-as-string.js b/packages/docgen/src/test/get-type-as-string.js index 6ba09acd33df18..c85fb63e77e62f 100644 --- a/packages/docgen/src/test/get-type-as-string.js +++ b/packages/docgen/src/test/get-type-as-string.js @@ -5,104 +5,72 @@ const getType = require( '../get-type-as-string' ); describe( 'getType from JSDoc', () => { it( 'NameExpression', () => { - const type = getType( { - title: 'param', - description: 'description', - type: { - type: 'NameExpression', - name: 'Array', - }, - name: 'paramName', - } ); + const type = getType( 'Array' ); expect( type ).toBe( 'Array' ); } ); it( 'AllLiteral', () => { - const type = getType( { - title: 'param', - description: 'description', - type: { - type: 'AllLiteral', - }, - name: 'paramName', - } ); + const type = getType( '*' ); expect( type ).toBe( '*' ); } ); it( 'Applications', () => { - const type = getType( { - title: 'param', - description: 'description', - type: { - type: 'TypeApplication', - expression: { - type: 'NameExpression', - name: 'Array', - }, - applications: [ - { - type: 'NameExpression', - name: 'Object', - }, - { - type: 'NameExpression', - name: 'String', - }, - ], - }, - } ); - expect( type ).toBe( 'Array' ); + const type = getType( 'Record' ); + expect( type ).toBe( 'Record' ); + } ); + + it( 'JSDoc style array', () => { + const type = getType( 'Array.' ); + expect( type ).toBe( 'Array' ); } ); it( 'NullableType', () => { - const type = getType( { - title: 'param', - description: 'description', - type: { - type: 'NullableType', - expression: { - type: 'NameExpression', - name: 'string', - }, - prefix: true, - }, - name: 'paramName', - } ); + const type = getType( 'string?' ); expect( type ).toBe( '?string' ); } ); + it( 'optional type', () => { + const type = getType( 'string', true ); + expect( type ).toBe( '[string]' ); + } ); + it( 'RestType', () => { - const type = getType( { - title: 'param', - description: 'description', - type: { - type: 'RestType', - expression: { - type: 'NameExpression', - name: 'Function', - }, - }, - name: 'paramName', - } ); + const type = getType( '...Function' ); expect( type ).toBe( '...Function' ); } ); + it( 'Union', () => { + const type = getType( 'null|undefined' ); + expect( type ).toBe( '(null|undefined)' ); + } ); + + it( 'Union 2', () => { + const type = getType( '(null|undefined)' ); + expect( type ).toBe( '(null|undefined)' ); + } ); + + it( 'Union 3', () => { + const type = getType( 'a|b|c|d' ); + expect( type ).toBe( '(a|b|c|d)' ); + } ); + + it( 'Optional', () => { + const type = getType( 'string=' ); + expect( type ).toBe( '[string]' ); + } ); + + it( 'Member', () => { + const type = getType( `React.FC` ); + expect( type ).toBe( 'React.FC' ); + } ); + + it( 'Member import', () => { + const type = getType( `import('./react').WPComponent` ); + expect( type ).toBe( 'WPComponent' ); + } ); + it( 'RestType with UnionType', () => { - const type = getType( { - title: 'param', - description: 'description', - type: { - type: 'RestType', - expression: { - type: 'UnionType', - elements: [ - { type: 'NameExpression', name: 'Object' }, - { type: 'NameExpression', name: 'string' }, - ], - }, - }, - name: 'paramName', - } ); + const type = getType( '...(Object|string)' ); expect( type ).toBe( '...(Object|string)' ); } ); } ); diff --git a/packages/element/README.md b/packages/element/README.md index c1f819f6116f5f..621278cf0eeff7 100755 --- a/packages/element/README.md +++ b/packages/element/README.md @@ -180,7 +180,7 @@ _Related_ _Parameters_ -- _child_ (unknown type): Any renderable child, such as an element, string, or fragment. +- _child_ `WPElement`: 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_ (unknown type): Component's instance. +- _component_ `WPComponent`: Component's instance. # **forwardRef** @@ -301,7 +301,7 @@ Renders a given element into the target DOM node. _Parameters_ -- _element_ (unknown type): Element to render. +- _element_ `WPElement`: Element to render. - _target_ `HTMLElement`: DOM node into which element should be rendered. # **renderToString** @@ -310,7 +310,7 @@ Serializes a React element to string. _Parameters_ -- _element_ (unknown type): Element to serialize. +- _element_ `ReactNode`: Element to serialize. - _context_ `[Object]`: Context object. - _legacyContext_ `[Object]`: Legacy context object.