forked from WordPress/gutenberg
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
269 changed files
with
2,216 additions
and
1,126 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import docgen from 'react-docgen-typescript'; | ||
import glob from 'glob'; | ||
import fs from 'node:fs/promises'; | ||
import path from 'path'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import { generateMarkdownDocs } from './markdown/index.mjs'; | ||
|
||
const MANIFEST_GLOB = 'packages/components/src/**/docs-manifest.json'; | ||
|
||
// For consistency, options should generally match the options used in Storybook. | ||
const OPTIONS = { | ||
shouldExtractLiteralValuesFromEnum: true, | ||
shouldRemoveUndefinedFromOptional: true, | ||
propFilter: ( prop ) => | ||
prop.parent ? ! /node_modules/.test( prop.parent.fileName ) : true, | ||
savePropValueAsString: true, | ||
}; | ||
|
||
function getTypeDocsForComponent( { | ||
manifestPath, | ||
componentFilePath, | ||
displayName, | ||
} ) { | ||
const resolvedPath = path.resolve( | ||
path.dirname( manifestPath ), | ||
componentFilePath | ||
); | ||
|
||
const typeDocs = docgen.parse( resolvedPath, OPTIONS ); | ||
|
||
if ( typeDocs.length === 0 ) { | ||
throw new Error( | ||
`react-docgen-typescript could not generate any type docs from ${ resolvedPath }` | ||
); | ||
} | ||
|
||
const matchingTypeDoc = typeDocs.find( | ||
( obj ) => obj.displayName === displayName | ||
); | ||
|
||
if ( typeof matchingTypeDoc === 'undefined' ) { | ||
const unmatchedTypeDocs = typeDocs | ||
.map( ( obj ) => `\`${ obj.displayName }\`` ) | ||
.join( ', ' ); | ||
|
||
throw new Error( | ||
`react-docgen-typescript could not find type docs for ${ displayName } in ${ resolvedPath }. (Found ${ unmatchedTypeDocs })` | ||
); | ||
} | ||
|
||
return matchingTypeDoc; | ||
} | ||
|
||
async function parseManifest( manifestPath ) { | ||
try { | ||
return JSON.parse( await fs.readFile( manifestPath, 'utf8' ) ); | ||
} catch ( e ) { | ||
throw new Error( | ||
`Error parsing docs manifest at ${ manifestPath }: ${ e.message }` | ||
); | ||
} | ||
} | ||
|
||
const manifests = glob.sync( MANIFEST_GLOB ); | ||
|
||
await Promise.all( | ||
manifests.map( async ( manifestPath ) => { | ||
const manifest = await parseManifest( manifestPath ); | ||
|
||
const typeDocs = getTypeDocsForComponent( { | ||
manifestPath, | ||
componentFilePath: manifest.filePath, | ||
displayName: manifest.displayName, | ||
} ); | ||
|
||
const subcomponentTypeDocs = manifest.subcomponents?.map( | ||
( subcomponent ) => { | ||
const docs = getTypeDocsForComponent( { | ||
manifestPath, | ||
componentFilePath: subcomponent.filePath, | ||
displayName: subcomponent.displayName, | ||
} ); | ||
|
||
if ( subcomponent.preferredDisplayName ) { | ||
docs.displayName = subcomponent.preferredDisplayName; | ||
} | ||
|
||
return docs; | ||
} | ||
); | ||
const docs = generateMarkdownDocs( { typeDocs, subcomponentTypeDocs } ); | ||
const outputFile = path.resolve( | ||
path.dirname( manifestPath ), | ||
'./README.md' | ||
); | ||
|
||
try { | ||
console.log( `Writing docs to ${ outputFile }` ); | ||
return fs.writeFile( outputFile, docs ); | ||
} catch ( e ) { | ||
throw new Error( | ||
`Error writing docs to ${ outputFile }: ${ e.message }` | ||
); | ||
} | ||
} ) | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import json2md from 'json2md'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import { generateMarkdownPropsJson } from './props.mjs'; | ||
|
||
export function generateMarkdownDocs( { typeDocs, subcomponentTypeDocs } ) { | ||
const mainDocsJson = [ | ||
{ h1: typeDocs.displayName }, | ||
'<!-- This file is generated automatically and cannot be edited directly. Make edits via TypeScript types and TSDocs. -->', | ||
{ | ||
p: `<p class="callout callout-info">See the <a href="https://wordpress.github.io/gutenberg/?path=/docs/components-${ typeDocs.displayName.toLowerCase() }--docs">WordPress Storybook</a> for more detailed, interactive documentation.</p>`, | ||
}, | ||
typeDocs.description, | ||
...generateMarkdownPropsJson( typeDocs.props ), | ||
]; | ||
|
||
const subcomponentDocsJson = subcomponentTypeDocs?.length | ||
? [ | ||
{ h2: 'Subcomponents' }, | ||
...subcomponentTypeDocs.flatMap( ( subcomponentTypeDoc ) => [ | ||
{ | ||
h3: subcomponentTypeDoc.displayName, | ||
}, | ||
subcomponentTypeDoc.description, | ||
...generateMarkdownPropsJson( subcomponentTypeDoc.props, { | ||
headingLevel: 4, | ||
} ), | ||
] ), | ||
] | ||
: []; | ||
|
||
return json2md( | ||
[ ...mainDocsJson, ...subcomponentDocsJson ].filter( Boolean ) | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
function renderPropType( type ) { | ||
const MAX_ENUM_VALUES = 10; | ||
|
||
switch ( type.name ) { | ||
case 'enum': { | ||
const string = type.value | ||
.slice( 0, MAX_ENUM_VALUES ) | ||
.map( ( { value } ) => value ) | ||
.join( ' | ' ); | ||
|
||
if ( type.value.length > MAX_ENUM_VALUES ) { | ||
return `${ string } | ...`; | ||
} | ||
return string; | ||
} | ||
default: | ||
return type.name; | ||
} | ||
} | ||
|
||
export function generateMarkdownPropsJson( props, { headingLevel = 2 } = {} ) { | ||
const sortedKeys = Object.keys( props ).sort( ( [ a ], [ b ] ) => | ||
a.localeCompare( b ) | ||
); | ||
|
||
const propsJson = sortedKeys | ||
.flatMap( ( key ) => { | ||
const prop = props[ key ]; | ||
|
||
if ( prop.description?.includes( '@ignore' ) ) { | ||
return null; | ||
} | ||
|
||
return [ | ||
{ [ `h${ headingLevel + 1 }` ]: `\`${ key }\`` }, | ||
prop.description, | ||
{ | ||
ul: [ | ||
`Type: \`${ renderPropType( prop.type ) }\``, | ||
`Required: ${ prop.required ? 'Yes' : 'No' }`, | ||
prop.defaultValue && | ||
`Default: \`${ prop.defaultValue.value }\``, | ||
].filter( Boolean ), | ||
}, | ||
]; | ||
} ) | ||
.filter( Boolean ); | ||
|
||
return [ { [ `h${ headingLevel }` ]: 'Props' }, ...propsJson ]; | ||
} | ||
|
Oops, something went wrong.