Summary: Inlining the component docs header instead of reading it from the same file we write to. Reviewed By: passy Differential Revision: D15199683 fbshipit-source-id: dc34e25b5f8073aa582f90a14bc875c8e8c431db
121 lines
3.3 KiB
JavaScript
121 lines
3.3 KiB
JavaScript
/**
|
|
* 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 HEADER = `---
|
|
id: ui-components
|
|
title: UI Components
|
|
---
|
|
|
|
Flipper has a lot of built in React components to build UIs. You can import them directly using e.g. \`import {Button} from 'flipper'\`.`;
|
|
|
|
const TARGET = __dirname + '/../docs/extending/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, HEADER + 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 || ''}
|
|
`;
|
|
}
|