@irwinproject/storybook-addon-tsdoc
Version:
Generate mdx documentation from your typescript!
218 lines (215 loc) • 13.6 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.render = exports.renderer = exports.build = exports.buildFromType = void 0;
const ts_morph_1 = require("ts-morph");
const TS_1 = __importDefault(require("./TS"));
const SyntaxKindDelegator_1 = require("./SyntaxKindDelegator");
const SyntaxKindDelegator_types_1 = __importDefault(require("./SyntaxKindDelegator.types"));
const decorators_1 = require("./decorators");
const console_log_colors_1 = require("console-log-colors");
const node_tools_1 = require("./node-tools");
const node_signature_1 = require("./node-signature");
const constants_1 = require("./constants");
const signitors_1 = require("./signitors");
/**
* @param node
*/
const shouldCrawl = (node) => {
return ts_morph_1.Node.isTypeLiteral(node) || ts_morph_1.Node.isObjectBindingPattern(node) || ts_morph_1.Node.isObjectLiteralExpression(node) || ts_morph_1.Node.isFunctionExpression(node) || ts_morph_1.Node.isArrowFunction(node);
};
/**
* Standardizes the properties handled by different function like declarations and expressions.
* @param typeParams
* @param args
* @param returnNode
* @returns
*/
const renderFNDetails = (node) => {
const tp = (0, exports.build)(...node.getTypeParameters());
const ag = (0, exports.build)(...node.getParameters());
const rt = (0, exports.build)(node.getReturnTypeNode()) || (0, exports.buildFromType)(node.getReturnType()) || (0, decorators_1.$literal)('void');
return [
...tp ? [
(0, decorators_1.$section)(constants_1.SEP, (0, decorators_1.$h)(5, undefined, "Type Arguments:"), tp)
] : [],
...ag ? [
(0, decorators_1.$section)(constants_1.SEP, (0, decorators_1.$t)(5) `Arguments:`, ag)
] : [],
...rt ? [
(0, decorators_1.$section)(constants_1.SEP, (0, decorators_1.$h)(5, undefined, "Returns:"), rt)
] : []
];
};
/**
* Just a convenient way to combine strings as blocks of text within a file.
* @param blocks
* @returns
*/
const block = (...blocks) => blocks.filter(b => b.trim()).join('\n');
/**
* Just a convenience to be used to generate a section from a node or nodes if any content is generated.
* @param nodes
* @returns
*/
const $sec = (...nodes) => (0, decorators_1.$section)((0, exports.build)(...nodes));
/**
* This method will act as a central point to interface with JSDocs.
*
* *note:* Some tags will require more integrated support and at this time I am still deciding on the precedent JSDoc should get vs typescript declarations.
*
* But this is a place I can modify the code for all doc blocks in a centeral location
* @param node
* @returns
*/
const getDocs = (node) => block((0, node_tools_1.getComments)(node), (0, node_tools_1.getExample)(node));
/**
* Tweaking node handling via syntax kind
*/
const RENDER_MAP = {
[SyntaxKindDelegator_types_1.default.TypeAliasDeclaration]: node => block((0, decorators_1.$s)(2, 'type', node), getDocs(node), $sec(...node.getTypeParameters(), node.getTypeNode())),
[SyntaxKindDelegator_types_1.default.TupleType]: node => (0, exports.build)(...node.getElements()),
[SyntaxKindDelegator_types_1.default.NamedTupleMember]: node => block((0, decorators_1.$s)(4, 'tuple item', node), getDocs(node), (0, decorators_1.$section)((0, exports.build)(node.getTypeNode()))),
[SyntaxKindDelegator_types_1.default.TypeLiteral]: node => {
const members = (0, exports.build)(...node.getMembers());
return members ? (0, decorators_1.$section)((0, decorators_1.$t)(4) `Members: `, members) : '';
},
[SyntaxKindDelegator_types_1.default.PropertySignature]: node => block((0, decorators_1.$s)(4, 'property', node), getDocs(node), $sec((0, node_tools_1.getTypeNode)(node))),
[SyntaxKindDelegator_types_1.default.PropertyDeclaration]: node => {
const tn = (0, node_tools_1.getTypeNode)(node);
return block((0, decorators_1.$s)(4, `${node.isStatic() ? 'static ' : ''}property`, node), getDocs(node), (tn && shouldCrawl(tn)) ? $sec((0, node_tools_1.getTypeNode)(node)) : '');
},
[SyntaxKindDelegator_types_1.default.MethodSignature]: node => {
return block((0, decorators_1.$s)(4, `method`, node), getDocs(node), ...renderFNDetails(node));
},
//unlike the method signature
[SyntaxKindDelegator_types_1.default.FunctionType]: node => block(...renderFNDetails(node)),
[SyntaxKindDelegator_types_1.default.ArrowFunction]: node => block(...renderFNDetails(node)),
[SyntaxKindDelegator_types_1.default.FunctionExpression]: node => block(...renderFNDetails(node)),
[SyntaxKindDelegator_types_1.default.MethodDeclaration]: node => {
return block((0, decorators_1.$s)(4, `${node.isStatic() ? 'static ' : ''}method`, node), getDocs(node), ...renderFNDetails(node));
},
[SyntaxKindDelegator_types_1.default.Parameter]: node => {
return block((0, decorators_1.$s)(4, 'argument', node), getDocs(node));
},
[SyntaxKindDelegator_types_1.default.TypeParameter]: node => {
const constraint = (0, exports.build)(node.getConstraint());
if (!constraint)
return '';
return block((0, decorators_1.$s)(4, 'param', node), getDocs(node), (0, decorators_1.$section)(constraint));
},
[SyntaxKindDelegator_types_1.default.TypeReference]: (node) => (0, node_signature_1.sig)(node), //this should be referenced in a parent signature I am not sure it should be traversed.
[SyntaxKindDelegator_types_1.default.UnionType]: node => {
const tns = node.getTypeNodes().filter(n => ts_morph_1.Node.isObjectBindingPattern(node) || ts_morph_1.Node.isObjectLiteralExpression(node));
return tns.length ? (0, exports.build)(...tns) : '';
},
[SyntaxKindDelegator_types_1.default.IntersectionType]: node => (0, exports.build)(...node.getTypeNodes()),
[SyntaxKindDelegator_types_1.default.ArrayType]: node => (0, exports.build)(node.getElementTypeNode()),
[SyntaxKindDelegator_types_1.default.ClassDeclaration]: node => block((0, decorators_1.$s)(2, 'class', node), getDocs(node), (0, decorators_1.$section)((0, exports.build)(...node.getConstructors()), (0, exports.build)(...node.getStaticBlocks()), (0, exports.build)(...node.getStaticProperties()), (0, exports.build)(...node.getStaticMethods()), (0, exports.build)(...node.getInstanceProperties()), (0, exports.build)(...node.getInstanceMethods()))),
[SyntaxKindDelegator_types_1.default.ClassStaticBlockDeclaration]: node => {
const comments = (0, node_tools_1.getComments)(node).trim();
return comments ? block((0, decorators_1.$t)(4) `${(0, decorators_1.$kd) `static block`}`, comments, (0, node_tools_1.getExample)(node)) : '';
},
[SyntaxKindDelegator_types_1.default.Constructor]: node => block((0, decorators_1.$s)(4, 'constructor', node), getDocs(node)),
[SyntaxKindDelegator_types_1.default.InterfaceDeclaration]: node => {
const typeParams = (0, exports.build)(...node.getTypeParameters());
const extensions = (0, exports.build)(...node.getExtends());
return block((0, decorators_1.$s)(4, 'interface', node), ...(typeParams ? [
'---',
(0, decorators_1.$t)(5) `Type Parameters`,
(0, decorators_1.$section)(typeParams)
] : []), ...(extensions ? [
'---',
(0, decorators_1.$t)(5) `Extends:`,
(0, decorators_1.$section)(extensions)
] : []), '---', (0, decorators_1.$t)(5) `Members:`, $sec(...node.getMembers()));
},
[SyntaxKindDelegator_types_1.default.ExpressionWithTypeArguments]: node => (0, exports.build)(...node.getTypeArguments()),
[SyntaxKindDelegator_types_1.default.GetAccessor]: node => block((0, decorators_1.$s)(4, (node.isStatic() ? 'static ' : '') + 'get', node), (0, node_tools_1.getComments)(node), (0, node_tools_1.getExample)(node)),
[SyntaxKindDelegator_types_1.default.SetAccessor]: node => block((0, decorators_1.$s)(4, (node.isStatic() ? 'static ' : '') + 'set', node), (0, node_tools_1.getComments)(node), (0, node_tools_1.getExample)(node)),
[SyntaxKindDelegator_types_1.default.ConditionalType]: node => (0, exports.build)(node.getExtendsType(), node.getTrueType(), node.getFalseType()),
[SyntaxKindDelegator_types_1.default.VariableStatement]: node => (0, exports.build)(...node.getDeclarations()),
[SyntaxKindDelegator_types_1.default.VariableDeclaration]: node => {
var _a;
const statement = node.getVariableStatement();
if (!statement)
return '';
return block((0, decorators_1.$s)(4, (0, decorators_1.$kind)(statement.getDeclarationKind()), node), (0, node_tools_1.getComments)(statement), (0, node_tools_1.getExample)(statement), (0, exports.build)((_a = node.getTypeNode()) !== null && _a !== void 0 ? _a : node.getInitializer()));
},
[SyntaxKindDelegator_types_1.default.ObjectBindingPattern]: node => (0, exports.build)(...node.getElements()),
[SyntaxKindDelegator_types_1.default.BindingElement]: node => (0, exports.build)(node.getPropertyNameNode()),
[SyntaxKindDelegator_types_1.default.IndexedAccessType]: node => (0, exports.build)(node.getObjectTypeNode(), node.getIndexTypeNode()),
[SyntaxKindDelegator_types_1.default.FunctionDeclaration]: node => block((0, decorators_1.$s)(4, 'function', node), getDocs(node), ...renderFNDetails(node)),
[SyntaxKindDelegator_types_1.default.ExpressionStatement]: node => '', //expressions are blocks of logic. Currently I dont plan on handling these instances.
[SyntaxKindDelegator_types_1.default.ClassExpression]: node => block(getDocs(node), (0, decorators_1.$section)((0, exports.build)(...node.getConstructors()), (0, exports.build)(...node.getStaticBlocks()), (0, exports.build)(...node.getStaticProperties()), (0, exports.build)(...node.getStaticMethods()), (0, exports.build)(...node.getInstanceProperties()), (0, exports.build)(...node.getInstanceMethods()))),
[SyntaxKindDelegator_types_1.default.ArrayLiteralExpression]: node => (0, exports.build)(...node.getElements()),
[SyntaxKindDelegator_types_1.default.ObjectLiteralExpression]: node => block((0, decorators_1.$section)((0, exports.build)(...node.getProperties()))),
[SyntaxKindDelegator_types_1.default.PropertyAssignment]: node => block((0, decorators_1.$s)(4, 'property', node), getDocs(node), (0, exports.build)(node.getInitializer())),
[SyntaxKindDelegator_types_1.default.EnumDeclaration]: node => block((0, decorators_1.$s)(2, 'enum', node), getDocs(node), (0, exports.build)(...node.getMembers())),
[SyntaxKindDelegator_types_1.default.EnumMember]: node => block((0, decorators_1.$s)(4, 'enum item', node), getDocs(node)),
[SyntaxKindDelegator_types_1.default.NewExpression]: () => ``,
[SyntaxKindDelegator_types_1.default.ObjectKeyword]: () => '',
[SyntaxKindDelegator_types_1.default.TypePredicate]: () => '',
[SyntaxKindDelegator_types_1.default.CallExpression]: () => '',
[SyntaxKindDelegator_types_1.default.BinaryExpression]: () => '',
[SyntaxKindDelegator_types_1.default.Identifier]: () => ' '
};
//Again a way to ignore or not alert me of lacking support. it seems there should be an interface for this.
const IGNOREKINDS = new Set([
SyntaxKindDelegator_types_1.default.ExportDeclaration,
SyntaxKindDelegator_types_1.default.MultiLineCommentTrivia,
SyntaxKindDelegator_types_1.default.SingleLineCommentTrivia
]);
const buildFromType = (type) => {
const node = (0, node_tools_1.declarationOfType)(type);
//If no node is found not much else can be done
if (!node)
return `<h4 className="ts-doc-header">${(0, signitors_1.fromType)(type)}</h4>`;
//if this is not directly referencing another node then I guess the documentation needs to continue traversing from the returning declaration.
if (type.isAnonymous())
return (0, exports.build)(node);
//Just show a link.... just like from the signature.
return `<h4 className="ts-doc-header">${(0, signitors_1.fromType)(type)}</h4>`;
};
exports.buildFromType = buildFromType;
/**
* Builds based on a list of nodes.
* @param nodes
* @returns
*/
const build = (...nodes) => nodes.map(node => {
if (!TS_1.default.documentPrivate && (0, node_tools_1.isPrivate)(node))
return '';
const val = (0, SyntaxKindDelegator_1.bySyntax)(node, RENDER_MAP, n => {
if (!n)
return '';
//TS.err("No support", red(n.getKindName()), cyan(n.getKind()), n.getText(), getFullName(n));
return ' ';
});
return val;
}).filter(b => b).join('\n');
exports.build = build;
const renderer = (node, df = n => {
if (!n || (0, node_tools_1.isPrimitive)(n) || IGNOREKINDS.has(n.getKind()))
return '';
TS_1.default.err("No support", (0, console_log_colors_1.red)(n.getKindName()), (0, console_log_colors_1.yellow)(n.getKind()), (0, node_tools_1.getFullName)(n));
return '';
}) => (0, SyntaxKindDelegator_1.bySyntax)(node, RENDER_MAP, df);
exports.renderer = renderer;
/**
* A conveniece to render a source file.
* @param node
* @returns
*/
const render = (title, node) => {
var _a;
const data = (_a = node.getChildSyntaxList()) === null || _a === void 0 ? void 0 : _a.getChildren().map(c => (0, exports.renderer)(c)).filter(n => !!n.replace(/\s/g, '')).join('\n---\n');
if (!data)
return;
return `${constants_1.STORY_BOOK_BLOCK}
<Meta title="${title}"/>
${data}`;
};
exports.render = render;