sync-api-docs/generateMarkdown.js (282 lines of code) (raw):

/** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ const tokenizeComment = require('tokenize-comment'); const {formatTypeColumn, formatDefaultColumn} = require('./propFormatter'); const { formatMethodType, formatMethodName, formatMethodDescription, } = require('./methodFormatter'); const { formatMultiplePlatform, stringToInlineCodeForTable, maybeLinkifyType, maybeLinkifyTypeName, formatType, } = require('./utils'); // Formats an array of rows as a Markdown table function generateTable(rows) { const colWidths = new Map(); for (const row of rows) { for (const col of Object.keys(row)) { colWidths.set( col, Math.max(colWidths.get(col) || col.length, String(row[col]).length) ); } } if (!colWidths.size) { return ''; } let header = '|', divider = '|'; for (const [col, width] of colWidths) { header += ' ' + col.padEnd(width + 1) + '|'; divider += ' ' + '-'.repeat(width) + ' ' + '|'; } let result = header + '\n' + divider + '\n'; for (const row of rows) { result += '|'; for (const [col, width] of colWidths) { result += ' ' + String(row[col] || '').padEnd(width + 1) + '|'; } result += '\n'; } return result; } // Formats information about a prop function generateProp(propName, prop) { const infoTable = generateTable([ { Type: formatTypeColumn(prop), ...formatDefaultColumn(prop), }, ]); return ( '### ' + (prop.required ? '<div class="label required basic">Required</div>' : '') + '`' + propName + '`' + (prop.rnTags && prop.rnTags.platform ? formatMultiplePlatform(prop.rnTags.platform) : '') + '\n' + '\n' + (prop.description ? prop.description + '\n\n' : '') + infoTable ); } // Formats information about a prop function generateMethod(method, component) { let descriptionTokenized = ''; let header = 'Valid `params` keys are:'; let mdPoints = ''; if (method?.params[0]?.type?.raw) { let desc = method?.params[0]?.type?.raw; let len = method?.params[0]?.type?.signature?.properties?.length; descriptionTokenized = tokenizeComment(desc); if ( descriptionTokenized?.examples && descriptionTokenized?.examples.length === len ) { let obj = []; for (let i = 0; i < len; i++) { let newObj = method?.params[0]?.type?.signature?.properties[i]; newObj['description'] = descriptionTokenized?.examples[i]?.value; obj.push(newObj); } obj.map(item => { if (item.description.trim() !== 'missing') mdPoints += `- '${item.key}' (${item.value.name}) - ${item.description}`; else mdPoints += `- '${item.key}' (${item.value.name})`; }); } } if (method?.docblock) { let dblock = method.docblock .split('\n') .map(line => { return line.replace(/ /, ''); }) .join('\n'); const docblockTokenized = tokenizeComment(dblock); dblock = dblock.replace(/@platform .*/g, ''); method.rnTags = {}; const platformTag = docblockTokenized.tags.find( ({key}) => key === 'platform' ); if (platformTag) { method.rnTags.platform = platformTag.value.split(','); } } return ( '### `' + method.name + '()`' + (method.rnTags && method.rnTags.platform ? formatMultiplePlatform(method.rnTags.platform) : '') + '\n' + '\n' + (method.description ? method.description + '\n\n' : '') + generateMethodSignatureTable(method, component) + (mdPoints && header + '\n' + mdPoints) ).trim(); } function lowerFirst(s) { return s[0].toLowerCase() + s.slice(1); } function generateMethodSignatureBlock(method, component) { return ( '```jsx\n' + (method.modifiers.includes('static') ? component.displayName + '.' : lowerFirst(component.displayName + '.')) + method.name + '(' + method.params .map(param => (param.optional ? `[${param.name}]` : param.name)) .join(', ') + ');' + '\n' + '```\n\n' ); } function generateMethodSignatureTable(method, component) { if (!method.params.length) { return ''; } return ( '**Parameters:**\n\n' + generateTable( method.params.map(param => { return { Name: formatMethodName(param), Type: formatMethodType(param), Required: param.optional ? 'No' : 'Yes', ...(param.description && { Description: formatMethodDescription(param), }), }; }) ) ); } // Formats information about props function generateProps({props, composes}) { if (!props || !Object.keys(props).length) { return ''; } return ( '## Props' + '\n' + '\n' + (composes && composes.length ? composes .map(parent => 'Inherits ' + maybeLinkifyTypeName(parent) + '.') .join('\n\n') + '\n\n' : '') + Object.keys(props) .sort((a, b) => a.localeCompare(b)) .sort((a, b) => props[b].required - props[a].required) .map(function (propName) { return generateProp(propName, props[propName]); }) .join('\n\n---\n\n') ); } function generateMethods(component) { const {methods} = component; if (!methods || !methods.length) { return ''; } return ( '## Methods' + '\n' + '\n' + [...methods] .sort((a, b) => a.name.localeCompare( b.name /* TODO @nocommit what's a neutral locale */ ) ) .map(function (method) { return generateMethod(method, component); }) .join('\n\n---\n\n') ); } // Generates a Docusaurus header for a component page function generateHeader({id, title}) { return ( '---' + '\n' + 'id: ' + id + '\n' + 'title: ' + title + '\n' + '---' + '\n' ); } // Function to process example contained description function preprocessDescription(desc) { // Playground tabs for the class and functional components const playgroundTab = `<div class="toggler"> <ul role="tablist" class="toggle-syntax"> <li id="functional" class="button-functional" aria-selected="false" role="tab" tabindex="0" aria-controls="functionaltab" onclick="displayTabs('syntax', 'functional')"> Function Component Example </li> <li id="classical" class="button-classical" aria-selected="false" role="tab" tabindex="0" aria-controls="classicaltab" onclick="displayTabs('syntax', 'classical')"> Class Component Example </li> </ul> </div>`; //Blocks for different syntax sections const functionalBlock = `<block class='functional syntax' />`; const classBlock = `<block class='classical syntax' />`; const endBlock = `<block class='endBlock syntax' />`; desc = desc .split('\n') .map(line => { return line.replace(/ /, ''); }) .join('\n'); const descriptionTokenized = tokenizeComment(desc); // Tabs counter for examples let tabs = 0; descriptionTokenized.examples.map(item => { const matchSnackPlayer = item.language.match(/(SnackPlayer name=).*/g); if (matchSnackPlayer) { const matchClassComp = matchSnackPlayer[0].match( /Class%20Component%20Example/ ); const matchFuncComp = matchSnackPlayer[0].match( /Function%20Component%20Example/ ); if (matchClassComp || matchFuncComp) tabs++; } }); if (tabs === 2) { const firstExample = desc.slice(desc.search('```SnackPlayer') + 1); const secondExample = firstExample.slice( firstExample.search('```SnackPlayer') + 1 ); return ( desc.substring(0, desc.search('```SnackPlayer')) + `\n## Example\n` + `${playgroundTab}\n\n${functionalBlock}\n\n${ '`' + firstExample.slice(0, firstExample.search('```') + 3) }\n\n${classBlock}\n\n${ '`' + secondExample.slice(0, secondExample.search('```') + 3) }\n\n${endBlock}` + secondExample.slice(secondExample.search('```') + 3) ); } else { if (desc.search('```SnackPlayer') !== -1) { return ( desc.slice(0, desc.search('```SnackPlayer')) + '\n' + '\n## Example\n' + '\n' + desc.slice(desc.search('```SnackPlayer')) ); } else return desc; } } function generateMarkdown({id, title}, component) { const markdownString = generateHeader({id, title}) + '\n' + preprocessDescription(component.description) + '\n\n' + '---\n\n' + '# Reference\n\n' + generateProps(component) + generateMethods(component); return markdownString.replace(/\n{3,}/g, '\n\n'); } module.exports = generateMarkdown;