auto generated UI docs
Summary: Using `react-docgen` to automatically generate docs for our UI components. React-docgen parses the files in `components/ui` and using comments and flowtypes to generate structured information about the UI components. For higher-order-components `react-docgen` is not able to get the information. As we are using HOC often as part of emotion's styled-components, I added a custom parser for HOCs. Using babel/parser I am parsing the components and using their doc-comments to generate the structured description. Then, the description generated by react-docgen and my custom parser is used to generate markdown which is added to the `ui-components.md` file of our docs. Plus: Added some styling around the documentation of props, mostly table styles. Reviewed By: jknoxville Differential Revision: D12896110 fbshipit-source-id: 1337c924c06e10a5d3aa752579428fd2cc7b4743
This commit is contained in:
committed by
Facebook Github Bot
parent
ac6575850e
commit
640dee6645
113
website/generate-uidocs.js
Normal file
113
website/generate-uidocs.js
Normal file
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* Copyright 2018-present Facebook.
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
* @format
|
||||
*/
|
||||
|
||||
const reactDocs = require('react-docgen');
|
||||
const glob = require('glob');
|
||||
const fs = require('fs');
|
||||
const babylon = require('@babel/parser');
|
||||
const docblockParser = require('docblock-parser');
|
||||
|
||||
const TARGET = __dirname + '/../docs/ui-components.md';
|
||||
|
||||
glob(__dirname + '/../src/ui/components/**/*.js', (err, files) => {
|
||||
const content = files
|
||||
.map(f => [f, fs.readFileSync(f)])
|
||||
.map(([name, file]) => {
|
||||
try {
|
||||
const doc = reactDocs.parse(file);
|
||||
console.log(`✅ ${name}`);
|
||||
return doc;
|
||||
} catch (e) {
|
||||
const doc = parseHOC(name, file);
|
||||
if (doc) {
|
||||
console.log(`✅ HOC: ${name}`);
|
||||
return doc;
|
||||
} else {
|
||||
console.error(`❌ ${name}: ${e.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
})
|
||||
.filter(Boolean)
|
||||
.map(generateMarkdown)
|
||||
.reduce((acc, cv) => acc + cv, '');
|
||||
fs.writeFileSync(TARGET, fs.readFileSync(TARGET) + content);
|
||||
});
|
||||
|
||||
// HOC are not supported by react-docgen. This means, styled-components will not
|
||||
// work. This is why we implement our own parser to information from these HOCs.
|
||||
function parseHOC(name, file) {
|
||||
try {
|
||||
const ast = babylon.parse(file.toString(), {
|
||||
sourceType: 'module',
|
||||
plugins: ['flow', 'objectRestSpread', 'classProperties'],
|
||||
});
|
||||
|
||||
// find the default export from the file
|
||||
const exportDeclaration = ast.program.body.find(
|
||||
node => node.type === 'ExportDefaultDeclaration',
|
||||
);
|
||||
|
||||
if (exportDeclaration) {
|
||||
// find doc comment right before the export
|
||||
const comment = ast.comments.find(
|
||||
c => c.end + 1 === exportDeclaration.start,
|
||||
);
|
||||
if (comment) {
|
||||
return {
|
||||
// use the file's name as name for the component
|
||||
displayName: name
|
||||
.split('/')
|
||||
.reverse()[0]
|
||||
.replace(/\.js$/, ''),
|
||||
description: docblockParser.parse(comment.value).text,
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
function generateMarkdown(component) {
|
||||
let props;
|
||||
if (component.props && Object.keys(component.props).length > 0) {
|
||||
props = '| Property | Type | Description |\n';
|
||||
props += '|---------|------|-------------|\n';
|
||||
Object.keys(component.props).forEach(prop => {
|
||||
let {flowType, description} = component.props[prop];
|
||||
|
||||
let type = '';
|
||||
if (flowType) {
|
||||
if (flowType.nullable) {
|
||||
type += '?';
|
||||
}
|
||||
|
||||
type +=
|
||||
flowType.name === 'signature' ||
|
||||
flowType.name === 'union' ||
|
||||
flowType.name === 'Array'
|
||||
? flowType.raw
|
||||
: flowType.name;
|
||||
}
|
||||
|
||||
// escape pipes and new lines because they will break tables
|
||||
type = type.replace(/\n/g, ' ').replace(/\|/g, '|');
|
||||
description = description
|
||||
? description.replace(/\n/g, ' ').replace(/\|/g, '|')
|
||||
: '';
|
||||
|
||||
props += `| \`${prop}\` | \`${type}\` | ${description} |\n`;
|
||||
});
|
||||
}
|
||||
return `
|
||||
## ${component.displayName}
|
||||
|
||||
${component.description || ''}
|
||||
|
||||
${props || ''}
|
||||
`;
|
||||
}
|
||||
Reference in New Issue
Block a user