Skip to content

Instantly share code, notes, and snippets.

@jordanell
Last active July 10, 2020 20:16
Show Gist options
  • Save jordanell/fd8d3871db75cdf32138ca50bc7beb10 to your computer and use it in GitHub Desktop.
Save jordanell/fd8d3871db75cdf32138ca50bc7beb10 to your computer and use it in GitHub Desktop.
A script to parse React components and generate markdown files for their API documentation.
import fs from "fs";
import path from "path";
import { parse } from "react-docgen";
import buildMarkdown from "./buildMarkdown";
async function generate(options = {}) {
const { component: componentObject } = options;
// Load source code
const src = fs.readFileSync(path.resolve(__dirname, "path/to/components/Button.jsx"), "utf8");
// Get path object of the component
const pathObj = path.parse(componentObject.filename);
// Actually parse the react component
const reactAPI = parse(src, null, null, {
filename: 'Button.jsx'
});
// Generate a markdown string to represent the component
const markdown = buildMarkdown(reactAPI);
const markdownDirectory = path.resolve(
__dirname,
'../src/pages/components-api'
);
// Write the markdown to the needed file
fs.writeFileSync(
path.resolve(markdownDirectory, 'button.md'),
markdown
);
}
generate();
import { parse as parseDoctrine } from "doctrine";
function escapeCell(value) {
// As the pipe is use for the table structure
return value
.replace(/</g, "&lt;")
.replace(/`&lt;/g, "`<")
.replace(/\|/g, "\\|");
}
function generatePropDescription(prop) {
const { description, type } = prop;
const parsed = parseDoctrine(description, {
sloppy: true
});
// Two new lines result in a newline in the table.
// All other new lines must be eliminated to prevent markdown mayhem.
return escapeCell(parsed.description)
.replace(/(\r?\n){2}/g, "<br>")
.replace(/\r?\n/g, " ");
}
function generatePropType(type) {
switch (type.name) {
case "union":
case "enum": {
return (
type.value
.map(type2 => {
if (type.name === "enum") {
return escapeCell(type2.value);
}
return generatePropType(type2);
})
// Display one value per line as it's better for visibility.
.join("<br>&#124;&nbsp;")
);
}
default:
return type.name;
}
}
function generateProps(reactAPI) {
const header = "## Props";
let text = `${header}
| Name | Type | Default | Description |
|:-----|:-----|:--------|:------------|\n`;
text = Object.keys(reactAPI.props).reduce((textProps, propRaw) => {
const prop = getProp(reactAPI.props[propRaw];
const description = generatePropDescription(prop);
if (description === null) {
return textProps;
}
let defaultValue = "";
if (prop.defaultValue) {
defaultValue = `<span class="prop-default">${escapeCell(
prop.defaultValue.value.replace(/\r*\n/g, "")
)}</span>`;
}
let propName = propRaw;
if (prop.required) {
propName = `<span class="prop-name required">${propRaw}&nbsp;*</span>`;
} else {
propName = `<span class="prop-name">${propRaw}</span>`;
}
return `${textProps}| ${propName} | <span class="prop-type">${generatePropType(
prop.type
)}</span> | ${defaultValue} | ${description} |\n`;
}, text);
text = `${text}
Any other props supplied will be provided to the root element.`;
return text;
}
export default function buildMarkdown(reactAPI) {
return [
"<!--- This documentation is automatically generated, do not try to edit it. -->",
"",
`# Button API`,
"",
`<p class="description">The API documentation of the Button React component.</p>`,
"",
generateProps(reactAPI)
].join("\n");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment