Files
flipper/website/generate-uidocs.js
John Knox 263aba9f31 Add custom edit url for ui-components page
Summary:
This page is auto generated, so the default link gives a 404.
Pointing it to the script instead.

Reviewed By: passy

Differential Revision: D21325554

fbshipit-source-id: 5ede07daa6335ad0199a11c5483e328c3ca2c5e3
2020-04-30 07:13:47 -07:00

124 lines
3.5 KiB
JavaScript

/**
* 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.
*
* @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
custom_edit_url: 'https://github.com/facebook/flipper/blob/master/website/generate-uidocs.js'
---
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.mdx';
glob(__dirname + '/../desktop/app/src/ui/components/**/*.tsx', (err, files) => {
const content = files
.map(f => [f, fs.readFileSync(f)])
.map(([name, file]) => {
try {
const doc = reactDocs.parse(file, null, null, {filename: name});
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: ['typescript', '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 {tsType, description} = component.props[prop];
let type = '';
if (tsType) {
if (tsType.nullable) {
type += '?';
}
type +=
tsType.name === 'signature' ||
tsType.name === 'union' ||
tsType.name === 'Array'
? tsType.raw
: tsType.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 || ''}
`;
}