UNPKG

@irwinproject/storybook-addon-tsdoc

Version:
292 lines (291 loc) 13.7 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.declarationOfType = exports.isStatic = exports.renderCode = exports.getExample = exports.isPrimitive = exports.isMethodLike = exports.getDocPath = exports.getFName = exports.getNearestName = exports.getTypeNode = exports.isAsync = exports.getFamilyName = exports.getComments = exports.parseDoc = exports.parseTags = exports.OMITTED_TAGS = exports.getJsDocs = exports.isKeyword = exports.getSignatureName = exports.getFullName = exports.getName = exports.isPrivate = 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 console_log_colors_1 = require("console-log-colors"); const typescript_1 = require("typescript"); const utils_1 = require("./utils"); const decorators_1 = require("./decorators"); const node_signature_1 = require("./node-signature"); /** * Because there isnt a standard way to mark something private. * @param node * @returns */ const isPrivate = (node) => !node || (ts_morph_1.Node.isModifierable(node) && node.hasModifier(SyntaxKindDelegator_types_1.default.PrivateKeyword)) || (ts_morph_1.Node.isJSDocable(node) && !!node.getJsDocs().find(d => d.getTags().some(t => t.getTagName().toLowerCase() === 'private'))) || (ts_morph_1.Node.isVariableDeclaration(node) && (0, exports.isPrivate)(node.getVariableStatement())); exports.isPrivate = isPrivate; /** * Check if a node has getName method * @param node * @returns */ const hasName = (node) => 'getName' in node && typeof node.getName === 'function'; /** * Get a nodes name if one is available * @param node * @returns */ const getName = (node) => (node && hasName(node)) ? (0, utils_1.escape)(node.getName()) : ''; exports.getName = getName; /** * Gets a / delimited list of names from source to current node * @param node * @returns */ const getFullName = (node, delim = ".") => { const family = (0, exports.getFamilyName)(node, delim); return [family, (ts_morph_1.Node.isStaticable(node) && node.isStatic() ? 'static' : ''), (0, exports.getName)(node)].filter(a => a).join(delim); }; exports.getFullName = getFullName; /** * Gets the signature of a declaration. * @param node * @param delim * @returns */ const getSignatureName = (node, delim = ".") => { const family = (0, exports.getFamilyName)(node, delim); return `${[family, (0, exports.getName)(node)].filter(a => a).join(delim)}`; }; exports.getSignatureName = getSignatureName; const isKeyword = (node) => ts_morph_1.Node.isAnyKeyword(node) || ts_morph_1.Node.isInferKeyword(node) || ts_morph_1.Node.isNeverKeyword(node) || ts_morph_1.Node.isNumberKeyword(node) || ts_morph_1.Node.isObjectKeyword(node) //this should probably be handled differently. || ts_morph_1.Node.isStringKeyword(node) || ts_morph_1.Node.isSymbolKeyword(node) || ts_morph_1.Node.isBooleanKeyword(node) || ts_morph_1.Node.isUndefinedKeyword(node); exports.isKeyword = isKeyword; /** * Get the JSDocs if available. * to avoid potential duplicate documentation explicit corner cases should be used. * * - variableDeclarations. (the declarations JSDoc should be derived from the statement) * @param node * @returns */ const getJsDocs = (node) => (ts_morph_1.Node.isJSDocable(node) && node.getJsDocs()) || (ts_morph_1.Node.isVariableDeclaration(node) && (0, exports.getJsDocs)(node.getVariableStatement())) || []; exports.getJsDocs = getJsDocs; /** * Instead it seems better to just support JSDoc separately from the built in typing. As I integrate properties into the signature process I can omit them from here. */ exports.OMITTED_TAGS = new Set([ "example", "param", "returns" ]); /** * This method is to simplify some of the tag parsing into one place. however with some tags being designed to affect the actual typing in the linter and editor I need to experiment with how these tags effect typescript in different environments. * * To be clear this will only parse tags that just need to be displayed but do not effect the typing of the object. * @param doc */ const parseTags = (doc) => doc.getTags().filter(t => !exports.OMITTED_TAGS.has(t.getTagName())).map(t => { var _a; return (0, decorators_1.$t)(6) `${(0, decorators_1.$kd) `&#64;${t.getTagName()}`} ${ts_morph_1.Node.isJSDocTypeTag(t) ? ' : ' + (0, node_signature_1.getSignature)((_a = t.getTypeExpression()) === null || _a === void 0 ? void 0 : _a.getTypeNode()) : ''} ${t.getCommentText()}`; }).join('\n'); exports.parseTags = parseTags; const parseDoc = (doc) => { var _a; const tags = (0, exports.parseTags)(doc); return ((_a = doc.getComment()) !== null && _a !== void 0 ? _a : "") + (tags ? '\n\n' + tags + '\n---\n' : ''); }; exports.parseDoc = parseDoc; const getParameters = (node) => ts_morph_1.Node.isParametered(node) ? node.getParameters() : []; const getTags = (node, typeFilter) => (0, exports.getJsDocs)(node).flatMap(d => { const tags = d.getTags(); if (typeFilter) return typeFilter instanceof RegExp ? tags.filter(t => typeFilter.test(t.getTagName())) : tags.filter(t => typeFilter === t.getTagName()); return tags; }); const getJSDocParameters = (node) => { if (!node) return []; return node ? getTags(node, 'param') : []; }; const getParameterComment = (node) => { var _a; const parent = node.getParent(); const i = getParameters(parent).findIndex(el => el === node); const param = getJSDocParameters(ts_morph_1.Node.isExpression(parent) ? parent.getParent() : parent)[i]; return (_a = param === null || param === void 0 ? void 0 : param.getCommentText()) !== null && _a !== void 0 ? _a : ""; //no parameter index found }; const getComments = (node) => (ts_morph_1.Node.isParameterDeclaration(node) ? getParameterComment(node) : ts_morph_1.Node.isVariableDeclaration(node) ? (0, exports.getComments)(node.getVariableStatement()) : (ts_morph_1.Node.isJSDocable(node) ? node.getJsDocs() : []).map(exports.parseDoc).join('\n') + '\n').wrap('', '<br/>', false); exports.getComments = getComments; /** * Converts the ancestors into a family name. * @param node * @returns */ const getFamilyName = (node, delim = ".") => node.getAncestors().map(a => (0, exports.getName)(a)).filter(a => a).reverse().join(delim); exports.getFamilyName = getFamilyName; const ModMap = { [SyntaxKindDelegator_types_1.default.TypeAliasDeclaration]: (node) => [node.getName(), '', []], [SyntaxKindDelegator_types_1.default.PropertySignature]: (node, df) => { const [pre, post] = (0, SyntaxKindDelegator_1.bySyntax)(node.getParent(), ModMap, df); return [pre + '.' + node.getName(), post, []]; }, [SyntaxKindDelegator_types_1.default.TypeLiteral]: (node, df) => { const parent = node.getParent(); const [pre, post, children] = (0, SyntaxKindDelegator_1.bySyntax)(parent, ModMap, df); return [pre + (children.length > 1 ? '.' + children.findIndex(c => c === node) : ''), post, []]; }, [SyntaxKindDelegator_types_1.default.UnionType]: (node, df) => { const [pre, post] = (0, SyntaxKindDelegator_1.bySyntax)(node.getParent(), ModMap, df); return [pre, post, node.getTypeNodes()]; }, [SyntaxKindDelegator_types_1.default.TypeParameter]: (node, df) => { const [pre, post] = (0, SyntaxKindDelegator_1.bySyntax)(node.getParent(), ModMap, df); return [pre + '.' + node.getName(), post, []]; }, [SyntaxKindDelegator_types_1.default.Parameter]: (node, df) => { const [pre, post] = (0, SyntaxKindDelegator_1.bySyntax)(node.getParent(), ModMap, df); return [pre + '.' + node.getName(), post, []]; }, [SyntaxKindDelegator_types_1.default.FunctionType]: (node, df) => { const [pre, post] = (0, SyntaxKindDelegator_1.bySyntax)(node.getParent(), ModMap, df); return [pre, post, []]; } }; const isAsync = (node) => { if (!('isAsync' in node) || typeof node.isAsync !== 'function') return false; return node.isAsync(); }; exports.isAsync = isAsync; const getTypeNode = (node) => (ts_morph_1.Node.isTyped(node) && node.getTypeNode()) || ((ts_morph_1.Node.isInitializerExpressionGetable(node) || ts_morph_1.Node.isInitializerExpressionable(node)) ? node.getInitializer() : undefined); exports.getTypeNode = getTypeNode; /** * In some cases the named node is the parent node of the evaluated node this just climbs the node tree until it finds a name * @param node */ const getNearestName = (node) => { let name = (0, exports.getName)(node); while (node && !name) { node = node.getParent(); name = (0, exports.getName)(node); } return name; }; exports.getNearestName = getNearestName; const getFName = (node) => { const [pre, post] = (0, SyntaxKindDelegator_1.bySyntax)(node, ModMap, n => { if (!n) return ['', '', []]; TS_1.default.warn((0, console_log_colors_1.cyan)(n.getKindName())); return ['', '', []]; }); const nm = pre + post; if (nm) { TS_1.default.success("FName", (0, console_log_colors_1.green)(nm)); return nm; } return (0, exports.getFullName)(node); }; exports.getFName = getFName; /** * Gets the source of the node and returns a storybook formatted link to the documentation of said node if the node exists within the scope provided by the entry point. * @param node */ const getDocPath = (node) => { const src = node.getSourceFile().getFilePath(); const ref = TS_1.default.resolveUrl(src); if (!ref) return; const fn = (0, exports.getFullName)(node, TS_1.default.documentStyle === 'file' ? '' : '/'); if (TS_1.default.documentStyle === "declaration") return TS_1.default.resolveDocPath(ref + '/' + fn).toLowerCase(); //no need for deeplinking but more explicit naming return TS_1.default.resolveDocPath(ref) + (fn ? '#' + fn.toLowerCase() : '').toLowerCase(); }; exports.getDocPath = getDocPath; /** * With there being 4 to 5 different method like declarations it seems like a good way to reduce redundant code. * @param node * @returns */ const isMethodLike = (node) => { if (!node) return false; const k = node.getKind(); return new Set([ SyntaxKindDelegator_types_1.default.MethodSignature, SyntaxKindDelegator_types_1.default.MethodDeclaration, SyntaxKindDelegator_types_1.default.FunctionType ]).has(k); }; exports.isMethodLike = isMethodLike; /** * Checks to see if the Node is primitive * * update: using Kind value is simpler response as I am not confirming a specific type. * @param node * @returns */ const isPrimitive = (node) => { if (!node) return false; const k = node.getKind(); return new Set([ SyntaxKindDelegator_types_1.default.AnyKeyword, SyntaxKindDelegator_types_1.default.StringKeyword, SyntaxKindDelegator_types_1.default.StringLiteral, SyntaxKindDelegator_types_1.default.NumberKeyword, SyntaxKindDelegator_types_1.default.NumericLiteral, SyntaxKindDelegator_types_1.default.BooleanKeyword, SyntaxKindDelegator_types_1.default.TrueKeyword, SyntaxKindDelegator_types_1.default.FalseKeyword, SyntaxKindDelegator_types_1.default.BigIntKeyword, SyntaxKindDelegator_types_1.default.BigIntLiteral, SyntaxKindDelegator_types_1.default.LiteralType, SyntaxKindDelegator_types_1.default.NullKeyword, SyntaxKindDelegator_types_1.default.NeverKeyword, SyntaxKindDelegator_types_1.default.VoidKeyword, SyntaxKindDelegator_types_1.default.UndefinedKeyword, SyntaxKindDelegator_types_1.default.UnknownKeyword, SyntaxKindDelegator_types_1.default.ExportAssignment, //Just to ignore the warning it wont be used at this stage. SyntaxKindDelegator_types_1.default.ImportDeclaration ]).has(k); }; exports.isPrimitive = isPrimitive; const getExample = (node) => { const examples = (0, exports.getJsDocs)(node).flatMap(d => d.getTags().filter(t => t.getTagName() === "example").map(t => t.getComment())); return examples.map(exports.renderCode).join('\n'); }; exports.getExample = getExample; const renderCode = (code) => code ? `\`\`\`ts\n${(0, typescript_1.createPrinter)({ removeComments: false }).printFile((0, typescript_1.createSourceFile)("t.ts", code, typescript_1.ScriptTarget.Latest, false, typescript_1.ScriptKind.TS))}\n\`\`\`` : ''; exports.renderCode = renderCode; const isStatic = (node) => { if (!ts_morph_1.Node.isStaticable(node)) return false; return node.isStatic(); }; exports.isStatic = isStatic; /** * attempt to get a Node from the type declaration * @param type */ const declarationOfType = (type, onlyAnonymous = false) => { var _a, _b, _c, _d; if (onlyAnonymous && !type.isAnonymous()) return; const [symbolDec] = (_b = (_a = type.getSymbol()) === null || _a === void 0 ? void 0 : _a.getDeclarations()) !== null && _b !== void 0 ? _b : []; const [aliasDec] = (_d = (_c = type.getAliasSymbol()) === null || _c === void 0 ? void 0 : _c.getDeclarations()) !== null && _d !== void 0 ? _d : []; return symbolDec !== null && symbolDec !== void 0 ? symbolDec : aliasDec; }; exports.declarationOfType = declarationOfType;