[New Feature] Add web folder to support web documentation
This commit is contained in:
76
docs/sync-api-docs/extractDocsFromRN.js
Normal file
76
docs/sync-api-docs/extractDocsFromRN.js
Normal file
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Copyright (c) AiCure, 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
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs-extra');
|
||||
const glob = require('glob');
|
||||
const path = require('path');
|
||||
const reactDocs = require('@motiz88/react-native-docgen');
|
||||
|
||||
const GENERATE_ANNOTATION = '@' + 'generate-docs';
|
||||
|
||||
module.exports = extractDocsFromRN;
|
||||
|
||||
async function extractDocsFromRN(rnRoot) {
|
||||
// TODO: make implementation async
|
||||
|
||||
const allComponentFiles = glob.sync(
|
||||
path.join(rnRoot, '/Libraries/{Components,Image,}/**/*.js'),
|
||||
{
|
||||
nodir: true,
|
||||
absolute: true,
|
||||
}
|
||||
);
|
||||
|
||||
const docs = [];
|
||||
|
||||
for (const file of allComponentFiles) {
|
||||
const contents = fs.readFileSync(file, {encoding: 'utf-8'});
|
||||
if (!contents.includes(GENERATE_ANNOTATION)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(file);
|
||||
|
||||
const result = reactDocs.parse(
|
||||
contents,
|
||||
reactDocs.resolver.findAllComponentDefinitions,
|
||||
reactDocs.defaultHandlers.filter(
|
||||
handler => handler !== reactDocs.handlers.propTypeCompositionHandler
|
||||
),
|
||||
{filename: file}
|
||||
);
|
||||
|
||||
const filteredResult = result.filter(item => {
|
||||
if (item.description) return item;
|
||||
});
|
||||
|
||||
docs.push({
|
||||
file,
|
||||
component: cleanComponentResult(...filteredResult),
|
||||
});
|
||||
}
|
||||
|
||||
// Make sure output is JSON-safe
|
||||
const docsSerialized = JSON.parse(JSON.stringify(docs));
|
||||
await fs.writeFile(
|
||||
path.join(__dirname, 'extracted.json'),
|
||||
JSON.stringify(docsSerialized, null, 2),
|
||||
'utf8'
|
||||
);
|
||||
return docsSerialized;
|
||||
}
|
||||
|
||||
function cleanComponentResult(component) {
|
||||
return {
|
||||
...component,
|
||||
methods: component.methods.filter(method => !method.name.startsWith('_')),
|
||||
};
|
||||
}
|
||||
326
docs/sync-api-docs/generateMarkdown.js
Normal file
326
docs/sync-api-docs/generateMarkdown.js
Normal file
@@ -0,0 +1,326 @@
|
||||
/**
|
||||
* Copyright (c) AiCure, 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.
|
||||
*/
|
||||
const tokenizeComment = require('tokenize-comment');
|
||||
const {formatTypeColumn, formatDefaultColumn} = require('./propFormatter');
|
||||
const {
|
||||
formatMethodType,
|
||||
formatMethodName,
|
||||
formatMethodDescription,
|
||||
} = require('./methodFormatter');
|
||||
|
||||
const {
|
||||
formatMultiplePlatform,
|
||||
stringToInlineCodeForTable,
|
||||
maybeLinkifyType,
|
||||
maybeLinkifyTypeName,
|
||||
formatType,
|
||||
} = require('./utils');
|
||||
|
||||
// Formats an array of rows as a Markdown table
|
||||
function generateTable(rows) {
|
||||
const colWidths = new Map();
|
||||
for (const row of rows) {
|
||||
for (const col of Object.keys(row)) {
|
||||
colWidths.set(
|
||||
col,
|
||||
Math.max(colWidths.get(col) || col.length, String(row[col]).length)
|
||||
);
|
||||
}
|
||||
}
|
||||
if (!colWidths.size) {
|
||||
return '';
|
||||
}
|
||||
let header = '|',
|
||||
divider = '|';
|
||||
for (const [col, width] of colWidths) {
|
||||
header += ' ' + col.padEnd(width + 1) + '|';
|
||||
divider += ' ' + '-'.repeat(width) + ' ' + '|';
|
||||
}
|
||||
|
||||
let result = header + '\n' + divider + '\n';
|
||||
for (const row of rows) {
|
||||
result += '|';
|
||||
for (const [col, width] of colWidths) {
|
||||
result += ' ' + String(row[col] || '').padEnd(width + 1) + '|';
|
||||
}
|
||||
result += '\n';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Formats information about a prop
|
||||
function generateProp(propName, prop) {
|
||||
const infoTable = generateTable([
|
||||
{
|
||||
Type: formatTypeColumn(prop),
|
||||
...formatDefaultColumn(prop),
|
||||
},
|
||||
]);
|
||||
|
||||
return (
|
||||
'### ' +
|
||||
(prop.required ? '<div class="label required basic">Required</div>' : '') +
|
||||
'`' +
|
||||
propName +
|
||||
'`' +
|
||||
(prop.rnTags && prop.rnTags.platform
|
||||
? formatMultiplePlatform(prop.rnTags.platform)
|
||||
: '') +
|
||||
'\n' +
|
||||
'\n' +
|
||||
(prop.description ? prop.description + '\n\n' : '') +
|
||||
infoTable
|
||||
);
|
||||
}
|
||||
|
||||
// Formats information about a prop
|
||||
function generateMethod(method, component) {
|
||||
let descriptionTokenized = '';
|
||||
let header = 'Valid `params` keys are:';
|
||||
let mdPoints = '';
|
||||
if (method?.params[0]?.type?.raw) {
|
||||
let desc = method?.params[0]?.type?.raw;
|
||||
let len = method?.params[0]?.type?.signature?.properties?.length;
|
||||
descriptionTokenized = tokenizeComment(desc);
|
||||
|
||||
if (
|
||||
descriptionTokenized?.examples &&
|
||||
descriptionTokenized?.examples.length === len
|
||||
) {
|
||||
let obj = [];
|
||||
for (let i = 0; i < len; i++) {
|
||||
let newObj = method?.params[0]?.type?.signature?.properties[i];
|
||||
newObj['description'] = descriptionTokenized?.examples[i]?.value;
|
||||
obj.push(newObj);
|
||||
}
|
||||
|
||||
obj.map(item => {
|
||||
if (item.description.trim() !== 'missing')
|
||||
mdPoints += `- '${item.key}' (${item.value.name}) - ${item.description}`;
|
||||
else mdPoints += `- '${item.key}' (${item.value.name})`;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (method?.docblock) {
|
||||
let dblock = method.docblock
|
||||
.split('\n')
|
||||
.map(line => {
|
||||
return line.replace(/ /, '');
|
||||
})
|
||||
.join('\n');
|
||||
const docblockTokenized = tokenizeComment(dblock);
|
||||
dblock = dblock.replace(/@platform .*/g, '');
|
||||
method.rnTags = {};
|
||||
const platformTag = docblockTokenized.tags.find(
|
||||
({key}) => key === 'platform'
|
||||
);
|
||||
|
||||
if (platformTag) {
|
||||
method.rnTags.platform = platformTag.value.split(',');
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
'### `' +
|
||||
method.name +
|
||||
'()`' +
|
||||
(method.rnTags && method.rnTags.platform
|
||||
? formatMultiplePlatform(method.rnTags.platform)
|
||||
: '') +
|
||||
'\n' +
|
||||
'\n' +
|
||||
(method.description ? method.description + '\n\n' : '') +
|
||||
generateMethodSignatureTable(method, component) +
|
||||
(mdPoints && header + '\n' + mdPoints)
|
||||
).trim();
|
||||
}
|
||||
|
||||
function lowerFirst(s) {
|
||||
return s[0].toLowerCase() + s.slice(1);
|
||||
}
|
||||
|
||||
function generateMethodSignatureBlock(method, component) {
|
||||
return (
|
||||
'```jsx\n' +
|
||||
(method.modifiers.includes('static')
|
||||
? component.displayName + '.'
|
||||
: lowerFirst(component.displayName + '.')) +
|
||||
method.name +
|
||||
'(' +
|
||||
method.params
|
||||
.map(param => (param.optional ? `[${param.name}]` : param.name))
|
||||
.join(', ') +
|
||||
');' +
|
||||
'\n' +
|
||||
'```\n\n'
|
||||
);
|
||||
}
|
||||
|
||||
function generateMethodSignatureTable(method, component) {
|
||||
if (!method.params.length) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return (
|
||||
'**Parameters:**\n\n' +
|
||||
generateTable(
|
||||
method.params.map(param => {
|
||||
return {
|
||||
Name: formatMethodName(param),
|
||||
Type: formatMethodType(param),
|
||||
Required: param.optional ? 'No' : 'Yes',
|
||||
...(param.description && {
|
||||
Description: formatMethodDescription(param),
|
||||
}),
|
||||
};
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Formats information about props
|
||||
function generateProps({props, composes}) {
|
||||
if (!props || !Object.keys(props).length) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return (
|
||||
'## Props' +
|
||||
'\n' +
|
||||
'\n' +
|
||||
(composes && composes.length
|
||||
? composes
|
||||
.map(parent => 'Inherits ' + maybeLinkifyTypeName(parent) + '.')
|
||||
.join('\n\n') + '\n\n'
|
||||
: '') +
|
||||
Object.keys(props)
|
||||
.sort((a, b) => a.localeCompare(b))
|
||||
.sort((a, b) => props[b].required - props[a].required)
|
||||
.map(function (propName) {
|
||||
return generateProp(propName, props[propName]);
|
||||
})
|
||||
.join('\n\n---\n\n')
|
||||
);
|
||||
}
|
||||
|
||||
function generateMethods(component) {
|
||||
const {methods} = component;
|
||||
if (!methods || !methods.length) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return (
|
||||
'## Methods' +
|
||||
'\n' +
|
||||
'\n' +
|
||||
[...methods]
|
||||
.sort((a, b) =>
|
||||
a.name.localeCompare(
|
||||
b.name /* TODO @nocommit what's a neutral locale */
|
||||
)
|
||||
)
|
||||
.map(function (method) {
|
||||
return generateMethod(method, component);
|
||||
})
|
||||
.join('\n\n---\n\n')
|
||||
);
|
||||
}
|
||||
|
||||
// Generates a Docusaurus header for a component page
|
||||
function generateHeader({id, title}) {
|
||||
return (
|
||||
'---' + '\n' + 'id: ' + id + '\n' + 'title: ' + title + '\n' + '---' + '\n'
|
||||
);
|
||||
}
|
||||
|
||||
// Function to process example contained description
|
||||
function preprocessDescription(desc) {
|
||||
// Playground tabs for the class and functional components
|
||||
const playgroundTab = `<div class="toggler">
|
||||
<ul role="tablist" class="toggle-syntax">
|
||||
<li id="functional" class="button-functional" aria-selected="false" role="tab" tabindex="0" aria-controls="functionaltab" onclick="displayTabs('syntax', 'functional')">
|
||||
Function Component Example
|
||||
</li>
|
||||
<li id="classical" class="button-classical" aria-selected="false" role="tab" tabindex="0" aria-controls="classicaltab" onclick="displayTabs('syntax', 'classical')">
|
||||
Class Component Example
|
||||
</li>
|
||||
</ul>
|
||||
</div>`;
|
||||
|
||||
//Blocks for different syntax sections
|
||||
const functionalBlock = `<block class='functional syntax' />`;
|
||||
const classBlock = `<block class='classical syntax' />`;
|
||||
const endBlock = `<block class='endBlock syntax' />`;
|
||||
|
||||
desc = desc
|
||||
.split('\n')
|
||||
.map(line => {
|
||||
return line.replace(/ /, '');
|
||||
})
|
||||
.join('\n');
|
||||
|
||||
const descriptionTokenized = tokenizeComment(desc);
|
||||
// Tabs counter for examples
|
||||
let tabs = 0;
|
||||
descriptionTokenized.examples.map(item => {
|
||||
const matchSnackPlayer = item.language.match(/(SnackPlayer name=).*/g);
|
||||
if (matchSnackPlayer) {
|
||||
const matchClassComp = matchSnackPlayer[0].match(
|
||||
/Class%20Component%20Example/
|
||||
);
|
||||
const matchFuncComp = matchSnackPlayer[0].match(
|
||||
/Function%20Component%20Example/
|
||||
);
|
||||
if (matchClassComp || matchFuncComp) tabs++;
|
||||
}
|
||||
});
|
||||
|
||||
if (tabs === 2) {
|
||||
const firstExample = desc.slice(desc.search('```SnackPlayer') + 1);
|
||||
const secondExample = firstExample.slice(
|
||||
firstExample.search('```SnackPlayer') + 1
|
||||
);
|
||||
|
||||
return (
|
||||
desc.substring(0, desc.search('```SnackPlayer')) +
|
||||
`\n## Example\n` +
|
||||
`${playgroundTab}\n\n${functionalBlock}\n\n${
|
||||
'`' + firstExample.slice(0, firstExample.search('```') + 3)
|
||||
}\n\n${classBlock}\n\n${
|
||||
'`' + secondExample.slice(0, secondExample.search('```') + 3)
|
||||
}\n\n${endBlock}` +
|
||||
secondExample.slice(secondExample.search('```') + 3)
|
||||
);
|
||||
} else {
|
||||
if (desc.search('```SnackPlayer') !== -1) {
|
||||
return (
|
||||
desc.slice(0, desc.search('```SnackPlayer')) +
|
||||
'\n' +
|
||||
'\n## Example\n' +
|
||||
'\n' +
|
||||
desc.slice(desc.search('```SnackPlayer'))
|
||||
);
|
||||
} else return desc;
|
||||
}
|
||||
}
|
||||
|
||||
function generateMarkdown({id, title}, component) {
|
||||
const markdownString =
|
||||
generateHeader({id, title}) +
|
||||
'\n' +
|
||||
preprocessDescription(component.description) +
|
||||
'\n\n' +
|
||||
'---\n\n' +
|
||||
'# Reference\n\n' +
|
||||
generateProps(component) +
|
||||
generateMethods(component);
|
||||
|
||||
return markdownString.replace(/\n{3,}/g, '\n\n');
|
||||
}
|
||||
|
||||
module.exports = generateMarkdown;
|
||||
61
docs/sync-api-docs/magic.js
Normal file
61
docs/sync-api-docs/magic.js
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Copyright (c) AiCure, 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.
|
||||
*/
|
||||
|
||||
// Hard-coded knowledge about the OpenDBM codebase and how to document it,
|
||||
// beyond what is explicitly encoded in the react-docgen artifact
|
||||
// (generatedComponentApiDocs.js)
|
||||
|
||||
// Ideally this file should go away.
|
||||
|
||||
module.exports = {
|
||||
linkableTypeAliases: {
|
||||
NativeColorValue: {
|
||||
text: 'color',
|
||||
url: 'colors.md',
|
||||
},
|
||||
ViewProps: {
|
||||
text: 'View Props',
|
||||
url: 'view.md#props',
|
||||
},
|
||||
PressEvent: {
|
||||
text: 'PressEvent',
|
||||
url: 'pressevent.md',
|
||||
},
|
||||
'RefreshLayoutConsts.SIZE.DEFAULT': {
|
||||
text: 'RefreshControl.SIZE',
|
||||
url: 'refreshcontrol.md#refreshlayoutconstssize',
|
||||
},
|
||||
StatusBarAnimation: {
|
||||
text: 'StatusBarAnimation',
|
||||
url: 'statusbar#statusbaranimation',
|
||||
},
|
||||
StatusBarStyle: {
|
||||
text: 'StatusBarStyle',
|
||||
url: 'statusbar#statusbarstyle',
|
||||
},
|
||||
ReactNode: {
|
||||
text: 'React.Node',
|
||||
url: 'react-node.md',
|
||||
},
|
||||
TextStyleProps: {
|
||||
text: 'Text Style Props',
|
||||
url: 'text-style-props',
|
||||
},
|
||||
SectionT: {
|
||||
text: 'Section',
|
||||
url: 'sectionlist#section',
|
||||
},
|
||||
ViewStyleProps: {
|
||||
text: 'View Style Props',
|
||||
url: 'view-style-props',
|
||||
},
|
||||
Text: {
|
||||
text: 'Text',
|
||||
url: 'text#style',
|
||||
},
|
||||
},
|
||||
};
|
||||
63
docs/sync-api-docs/methodFormatter.js
Normal file
63
docs/sync-api-docs/methodFormatter.js
Normal file
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Copyright (c) AiCure, 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const {typeOf} = require('tokenize-comment/lib/utils');
|
||||
const magic = require('./magic');
|
||||
const {formatMultiplePlatform} = require('./utils');
|
||||
|
||||
function formatMethodType(param) {
|
||||
let text, url;
|
||||
if (param?.type?.name === 'union') {
|
||||
if (param?.type?.alias) {
|
||||
const {alias} = param.type;
|
||||
if (Object.hasOwnProperty.call(magic.linkableTypeAliases, alias)) {
|
||||
({url, text} = magic.linkableTypeAliases[alias]);
|
||||
}
|
||||
if (url) return `[${text}](${url})`;
|
||||
else return param.type.alias;
|
||||
}
|
||||
return param.type.name;
|
||||
} else {
|
||||
if (param?.type?.type) return param.type.type;
|
||||
else return param.type.name;
|
||||
}
|
||||
}
|
||||
|
||||
function formatMethodName(param) {
|
||||
let tag = param.description;
|
||||
if (tag) {
|
||||
const isMatch = tag.match(/{@platform [a-z ,]*}/);
|
||||
if (isMatch) {
|
||||
const platform = isMatch[0].match(/ [a-z ,]*/);
|
||||
tag = tag.replace(/{@platform [a-z ,]*}/g, '');
|
||||
tag = formatMultiplePlatform(platform[0].split(','));
|
||||
return param.name + tag;
|
||||
}
|
||||
}
|
||||
return param.name;
|
||||
}
|
||||
|
||||
function formatMethodDescription(param) {
|
||||
let tag = param.description;
|
||||
const isMatch = tag.match(/{@platform [a-z ,]*}/);
|
||||
if (isMatch) {
|
||||
const platform = isMatch[0].match(/ [a-z ,]*/);
|
||||
|
||||
// Replaces @platform strings with empty string
|
||||
// and appends type with formatted platform
|
||||
tag = tag.replace(/{@platform [a-z ,]*}/g, '');
|
||||
}
|
||||
return tag;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
formatMethodType,
|
||||
formatMethodName,
|
||||
formatMethodDescription,
|
||||
};
|
||||
18
docs/sync-api-docs/package.json
Normal file
18
docs/sync-api-docs/package.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "@react-native-website/sync-api",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"prettier": "prettier --write \"**/*.js\"",
|
||||
"sync": "node sync-api-docs ../../react-native"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@motiz88/react-native-docgen": "0.0.3",
|
||||
"fs-extra": "^9.1.0",
|
||||
"glob": "^7.1.6",
|
||||
"he": "^1.2.0",
|
||||
"path": "^0.12.7",
|
||||
"react-docgen-markdown-renderer": "^2.1.3",
|
||||
"tokenize-comment": "^3.0.1"
|
||||
}
|
||||
}
|
||||
85
docs/sync-api-docs/preprocessGeneratedApiDocs.js
Normal file
85
docs/sync-api-docs/preprocessGeneratedApiDocs.js
Normal file
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Copyright (c) AiCure, 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// Preprocess the react-docgen artifact before rendering it to Markdown.
|
||||
// This file may end up in the OpenDBM repo, as part of the
|
||||
// `generate-api-docs` script.
|
||||
|
||||
const tokenizeComment = require('tokenize-comment');
|
||||
const {typeOf} = require('tokenize-comment/lib/utils');
|
||||
|
||||
function joinDescriptionAndExamples(tokenized) {
|
||||
let sections = [];
|
||||
if (tokenized.description) {
|
||||
sections.push(tokenized.description);
|
||||
}
|
||||
for (const {raw} of tokenized.examples) {
|
||||
sections.push(raw);
|
||||
}
|
||||
if (tokenized.footer) {
|
||||
sections.push(tokenized.footer);
|
||||
}
|
||||
return sections.join('\n\n');
|
||||
}
|
||||
|
||||
function preprocessTagsInDescription(obj) {
|
||||
if (obj && obj.description) {
|
||||
obj.description = obj.description
|
||||
.split('\n')
|
||||
.map(line => {
|
||||
return line.replace(/ /, '');
|
||||
})
|
||||
.join('\n');
|
||||
const descriptionTokenized = tokenizeComment(obj.description);
|
||||
obj.description = obj.description.replace(
|
||||
/@platform .*|@default .*|@type .*/g,
|
||||
''
|
||||
);
|
||||
obj.rnTags = {};
|
||||
const platformTag = descriptionTokenized.tags.find(
|
||||
({key}) => key === 'platform'
|
||||
);
|
||||
const defaultTag = descriptionTokenized.tags.filter(
|
||||
tag => tag.key === 'default'
|
||||
);
|
||||
const typeTag = descriptionTokenized.tags.filter(tag => tag.key === 'type');
|
||||
|
||||
if (platformTag) {
|
||||
obj.rnTags.platform = platformTag.value.split(',');
|
||||
}
|
||||
if (defaultTag.length) {
|
||||
obj.rnTags.default = [];
|
||||
defaultTag.forEach(tag => {
|
||||
obj.rnTags.default.push(tag.value);
|
||||
});
|
||||
}
|
||||
if (typeTag.length) {
|
||||
obj.rnTags.type = [];
|
||||
typeTag.forEach(tag => {
|
||||
obj.rnTags.type.push(tag.value);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: This function mutates `docs`.
|
||||
function preprocessGeneratedApiDocs(docs) {
|
||||
for (const {component} of docs) {
|
||||
if (component.props && component.description) {
|
||||
for (const prop of Object.values(component.props)) {
|
||||
preprocessTagsInDescription(prop);
|
||||
}
|
||||
for (const prop of component.methods) {
|
||||
preprocessTagsInDescription(prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = preprocessGeneratedApiDocs;
|
||||
246
docs/sync-api-docs/propFormatter.js
Normal file
246
docs/sync-api-docs/propFormatter.js
Normal file
@@ -0,0 +1,246 @@
|
||||
/**
|
||||
* Copyright (c) AiCure, 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const {typeOf} = require('tokenize-comment/lib/utils');
|
||||
const magic = require('./magic');
|
||||
const {
|
||||
formatMultiplePlatform,
|
||||
stringToInlineCodeForTable,
|
||||
maybeLinkifyType,
|
||||
maybeLinkifyTypeName,
|
||||
formatType,
|
||||
} = require('./utils');
|
||||
|
||||
function formatTypeColumn(prop) {
|
||||
// Checks for @type pragma comment
|
||||
if (prop.rnTags && prop.rnTags.type) {
|
||||
let tableRows = '';
|
||||
const typeTags = prop.rnTags.type;
|
||||
|
||||
typeTags.forEach(tag => {
|
||||
let url, text;
|
||||
// Checks for @platform pragma in @type string
|
||||
const isMatch = tag.match(/{@platform [a-z ,]*}/);
|
||||
if (isMatch) {
|
||||
// Extracts platforms from matched regex
|
||||
const platform = isMatch[0].match(/ [a-z ,]*/);
|
||||
|
||||
// Replaces @platform strings with empty string
|
||||
// and appends type with formatted platform
|
||||
tag = tag.replace(/{@platform [a-z ,]*}/g, '');
|
||||
if (Object.hasOwnProperty.call(magic.linkableTypeAliases, tag)) {
|
||||
({url, text} = magic.linkableTypeAliases[tag]);
|
||||
if (url) tag = `[${text}](${url})`;
|
||||
}
|
||||
tag = tag + formatMultiplePlatform(platform[0].split(','));
|
||||
} else {
|
||||
// Check if there are multiple comma separated types in a single line
|
||||
if (tag.match(/, /)) {
|
||||
let newTag = '';
|
||||
const tags = tag.split(', ');
|
||||
tags.forEach(item => {
|
||||
if (Object.hasOwnProperty.call(magic.linkableTypeAliases, item)) {
|
||||
({url, text} = magic.linkableTypeAliases[item]);
|
||||
if (url) newTag += ', ' + `[${text}](${url})`;
|
||||
} else newTag += ', ' + item;
|
||||
});
|
||||
//Trim comma from beginning
|
||||
tag = newTag.replace(/^, /, '');
|
||||
}
|
||||
// If there is no comma separated types in rnTags
|
||||
else if (Object.hasOwnProperty.call(magic.linkableTypeAliases, tag)) {
|
||||
({url, text} = magic.linkableTypeAliases[tag]);
|
||||
if (url) tag = `[${text}](${url})`;
|
||||
}
|
||||
}
|
||||
tableRows = tableRows + tag + '<hr/>';
|
||||
});
|
||||
tableRows = tableRows.replace(/<hr\/>$/, '');
|
||||
return tableRows;
|
||||
}
|
||||
|
||||
// To extract type from prop flowType
|
||||
else if (prop.flowType && Object.keys(prop.flowType).length >= 1) {
|
||||
let text, url;
|
||||
|
||||
// Handles flowtype name for signatures
|
||||
if (prop.flowType.name === 'signature') {
|
||||
// Handles flowtype for function signature
|
||||
if (prop.flowType.type === 'function') {
|
||||
// Extracts EventType from the raw value
|
||||
const isMatch = prop.flowType.raw.match(/: [a-zA-Z]*/);
|
||||
if (isMatch) {
|
||||
// Formats EventType
|
||||
const eventType = isMatch[0].slice(2);
|
||||
// Checks for aliases in magic and generates md url
|
||||
if (
|
||||
Object.hasOwnProperty.call(magic.linkableTypeAliases, eventType)
|
||||
) {
|
||||
({url, text} = magic.linkableTypeAliases[eventType]);
|
||||
if (url) {
|
||||
return `${prop.flowType.type}([${text}](${url}))`;
|
||||
}
|
||||
}
|
||||
// TODO: Handling unknown function params
|
||||
return `${prop.flowType.type}`;
|
||||
} else {
|
||||
return prop.flowType.type;
|
||||
}
|
||||
} else if (prop.flowType.type === 'object') {
|
||||
return prop.flowType.type;
|
||||
}
|
||||
} else if (prop.flowType.name.includes('$ReadOnlyArray')) {
|
||||
prop?.flowType?.elements[0]?.elements &&
|
||||
prop?.flowType?.elements[0]?.elements.forEach(elem => {
|
||||
if (
|
||||
Object.hasOwnProperty.call(magic.linkableTypeAliases, elem.name)
|
||||
) {
|
||||
({url, text} = magic.linkableTypeAliases[elem.name]);
|
||||
}
|
||||
});
|
||||
if (url) return `array of [${text}](${url})`;
|
||||
else if (prop?.flowType?.elements[0].name === 'union') {
|
||||
const unionTypes = prop?.flowType?.elements[0]?.elements.reduce(
|
||||
(acc, curr) => {
|
||||
acc.push(curr.value);
|
||||
return acc;
|
||||
},
|
||||
[]
|
||||
);
|
||||
return `array of enum(${unionTypes.join(', ')})`;
|
||||
} else if (prop?.flowType?.elements[0]?.name) {
|
||||
const typeName = prop.flowType.elements[0].name;
|
||||
//array of number
|
||||
if (typeName === 'number') return `array of ${typeName}`;
|
||||
else if (
|
||||
Object.hasOwnProperty.call(magic.linkableTypeAliases, typeName)
|
||||
) {
|
||||
({url, text} = magic.linkableTypeAliases[typeName]);
|
||||
if (url) return `array of [${text}](${url})`;
|
||||
}
|
||||
//default array for all other types
|
||||
else return 'array';
|
||||
}
|
||||
} else if (prop.flowType.name === '$ReadOnly') {
|
||||
// Special Case: switch#trackcolor
|
||||
let markdown = '';
|
||||
if (prop.flowType.elements[0]?.type === 'object') {
|
||||
prop?.flowType?.elements[0]?.signature?.properties.forEach(
|
||||
({key, value}) => {
|
||||
value?.elements?.forEach(elem => {
|
||||
if (
|
||||
Object.hasOwnProperty.call(magic.linkableTypeAliases, elem.name)
|
||||
) {
|
||||
({url, text} = magic.linkableTypeAliases[elem.name]);
|
||||
markdown += `${key}: [${text}](${url})` + ', ';
|
||||
}
|
||||
});
|
||||
if (!url) markdown += `${key}: ${value.name}` + ', ';
|
||||
}
|
||||
);
|
||||
if (markdown.match(/, $/)) markdown = markdown.replace(/, $/, '');
|
||||
return `${prop.flowType.elements[0]?.type}: {${markdown}}`;
|
||||
}
|
||||
} else if (prop.flowType.name === 'union') {
|
||||
let unionTypes = prop.flowType.raw.split('|');
|
||||
|
||||
// Trim whitespaces and remove any leftover `|` (to avoid table split)
|
||||
unionTypes = unionTypes
|
||||
.map(elem => {
|
||||
return elem.trim().replace(/|/g, '');
|
||||
})
|
||||
.filter(item => {
|
||||
if (item) return item;
|
||||
});
|
||||
|
||||
// Get text and url from magic aliases
|
||||
prop?.flowType?.elements?.forEach(elem => {
|
||||
if (Object.hasOwnProperty.call(magic.linkableTypeAliases, elem.name)) {
|
||||
({url, text} = magic.linkableTypeAliases[elem.name]);
|
||||
}
|
||||
});
|
||||
|
||||
if (url) return `[${text}](${url})`;
|
||||
|
||||
return `enum(${unionTypes.join(', ')})`;
|
||||
} else if (prop.flowType.name === 'ReactElement') {
|
||||
return 'element';
|
||||
} else {
|
||||
// Get text and url from magic aliases
|
||||
prop?.flowType?.elements?.forEach(elem => {
|
||||
if (Object.hasOwnProperty.call(magic.linkableTypeAliases, elem.name)) {
|
||||
({url, text} = magic.linkableTypeAliases[elem.name]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// If no text is found, get raw values as text
|
||||
if (!text) {
|
||||
// TO BE FIXED
|
||||
text =
|
||||
(prop.flowType.raw && stringToInlineCodeForTable(prop.flowType.raw)) ||
|
||||
formatType(prop.flowType.name);
|
||||
}
|
||||
|
||||
// If URL is found, return text and link in markdown format
|
||||
if (url) {
|
||||
return `[${text}](${url})`;
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
// Adds proper markdown formatting to component's default value.
|
||||
function formatDefaultColumn(prop) {
|
||||
if (prop?.rnTags?.default) {
|
||||
// Parse from @default annotation
|
||||
let tableRows = '';
|
||||
prop.rnTags.default.forEach(tag => {
|
||||
const isMatch = tag.match(/{@platform [a-z]*}/);
|
||||
|
||||
if (isMatch) {
|
||||
const platform = isMatch[0].match(/ [a-z]*/);
|
||||
tag = tag.replace(/{@platform [a-z]*}/g, '');
|
||||
|
||||
// Checks component for NativeColorValue in default
|
||||
let colorBlock = '';
|
||||
prop?.flowType?.elements.some(elem => {
|
||||
if (elem.name === 'NativeColorValue' && !tag.includes('null')) {
|
||||
colorBlock = `<ins style={{background: '${tag.replace(
|
||||
/'/g,
|
||||
''
|
||||
)}'}} className="color-box" />`;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
tag =
|
||||
colorBlock +
|
||||
(!tag.includes('null') ? '`' + tag + '`' : tag) +
|
||||
formatMultiplePlatform(platform[0].split(','));
|
||||
} else if (!tag.includes('`')) {
|
||||
tag = '`' + tag + '`';
|
||||
}
|
||||
tableRows = tableRows + tag + '<hr/>';
|
||||
});
|
||||
tableRows = tableRows.replace(/<hr\/>$/, '');
|
||||
return {Default: tableRows};
|
||||
} else {
|
||||
// Parse defaultValue if @default annotation not provided
|
||||
return prop?.defaultValue?.value
|
||||
? {Default: stringToInlineCodeForTable(prop?.defaultValue?.value)}
|
||||
: '';
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
formatTypeColumn,
|
||||
formatDefaultColumn,
|
||||
};
|
||||
51
docs/sync-api-docs/sync-api-docs.js
Normal file
51
docs/sync-api-docs/sync-api-docs.js
Normal file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright (c) AiCure, 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.
|
||||
*/
|
||||
|
||||
// ***** EXPERIMENTAL *****
|
||||
// Updates the API docs from the OpenDBM source code.
|
||||
|
||||
'use strict';
|
||||
|
||||
const process = require('process');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
|
||||
const extractDocsFromRN = require('./extractDocsFromRN');
|
||||
const preprocessGeneratedApiDocs = require('./preprocessGeneratedApiDocs');
|
||||
const generateMarkdown = require('./generateMarkdown');
|
||||
const {titleToId} = require('./utils');
|
||||
|
||||
const DOCS_ROOT_DIR = path.resolve(__dirname, '..', 'docs');
|
||||
|
||||
async function generateApiDocs(rnPath) {
|
||||
const apiDocs = await extractDocsFromRN(rnPath);
|
||||
preprocessGeneratedApiDocs(apiDocs);
|
||||
await Promise.all(
|
||||
apiDocs.map(async ({component, file}, index) => {
|
||||
if (!component.displayName) {
|
||||
console.log(
|
||||
`react-docgen data for ${path.basename(file)} was malformed, skipping`
|
||||
);
|
||||
return;
|
||||
}
|
||||
const id = titleToId(component.displayName);
|
||||
const componentMarkdown = generateMarkdown(
|
||||
{title: component.displayName, id: id},
|
||||
component
|
||||
);
|
||||
const outFile = path.join(DOCS_ROOT_DIR, id + '.md');
|
||||
console.log('Generated ' + outFile);
|
||||
await fs.writeFile(outFile, componentMarkdown, 'utf8');
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async function main(args) {
|
||||
await generateApiDocs(args[0]);
|
||||
}
|
||||
|
||||
main(process.argv.slice(2));
|
||||
95
docs/sync-api-docs/utils.js
Normal file
95
docs/sync-api-docs/utils.js
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Copyright (c) AiCure, 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
const he = require('he');
|
||||
const magic = require('./magic');
|
||||
|
||||
// Adds multiple platform tags for prop name
|
||||
function formatMultiplePlatform(platforms) {
|
||||
let platformString = '';
|
||||
platforms.forEach(platform => {
|
||||
switch (platform.trim().toLowerCase()) {
|
||||
case 'ios':
|
||||
platformString += '<div class="label ios">' + 'iOS' + '</div> ';
|
||||
break;
|
||||
case 'android':
|
||||
platformString += '<div class="label android">' + 'Android' + '</div>';
|
||||
break;
|
||||
case 'tv':
|
||||
platformString += '<div class="label tv">' + 'TV' + '</div>';
|
||||
break;
|
||||
//TODO: Add a new CSS class for VR
|
||||
case 'vr':
|
||||
platformString += '<div class="label tv">' + 'VR' + '</div>';
|
||||
}
|
||||
});
|
||||
return platformString;
|
||||
}
|
||||
|
||||
// Wraps a string in an inline code block in a way that is safe to include in a
|
||||
// table cell, by wrapping it as HTML <code> if necessary.
|
||||
function stringToInlineCodeForTable(str) {
|
||||
let useHtml = /[`|]/.test(str);
|
||||
str = str.replace(/\n/g, ' ');
|
||||
if (useHtml) {
|
||||
return '<code>' + he.encode(str).replace(/\|/g, '|') + '</code>';
|
||||
}
|
||||
return '`' + str + '`';
|
||||
}
|
||||
|
||||
function maybeLinkifyType(flowType) {
|
||||
let url, text;
|
||||
flowType.elements?.forEach(elem => {
|
||||
if (Object.hasOwnProperty.call(magic.linkableTypeAliases, elem.name)) {
|
||||
({url, text} = magic.linkableTypeAliases[elem.name]);
|
||||
}
|
||||
});
|
||||
if (!text) {
|
||||
text = stringToInlineCodeForTable(
|
||||
flowType.raw || formatType(flowType.name)
|
||||
);
|
||||
}
|
||||
if (url) {
|
||||
return `[${text}](${url})`;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
function formatType(name) {
|
||||
if (name.toLowerCase() === 'boolean') return 'bool';
|
||||
if (name.toLowerCase() === 'stringish') return 'string';
|
||||
if (name === '$ReadOnlyArray') return 'array';
|
||||
return name;
|
||||
}
|
||||
|
||||
function maybeLinkifyTypeName(name) {
|
||||
let url, text;
|
||||
if (Object.hasOwnProperty.call(magic.linkableTypeAliases, name)) {
|
||||
({url, text} = magic.linkableTypeAliases[name]);
|
||||
}
|
||||
if (!text) {
|
||||
text = stringToInlineCodeForTable(name);
|
||||
}
|
||||
if (url) {
|
||||
return `[${text}](${url})`;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
function titleToId(title) {
|
||||
return title.toLowerCase().replace(/[^a-z]+/g, '-');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
formatMultiplePlatform,
|
||||
stringToInlineCodeForTable,
|
||||
maybeLinkifyType,
|
||||
maybeLinkifyTypeName,
|
||||
formatType,
|
||||
titleToId,
|
||||
};
|
||||
Reference in New Issue
Block a user