src/utils/docblock.ts (40 lines of code) (raw):

/** * Helper functions to work with docblock comments. */ import { CommentKind } from 'ast-types/gen/kinds'; import type { NodePath } from 'ast-types/lib/node-path'; const DOCLET_PATTERN = /^@(\w+)(?:$|\s((?:[^](?!^@\w))*))/gim; function parseDocblock(str: string): string { // Does not use \s in the regex as this would match also \n and conflicts // with windows line endings. return str.replace(/^[ \t]*\*[ \t]?/gm, '').trim(); } const DOCBLOCK_HEADER = /^\*\s/; /** * Given a path, this function returns the closest preceding docblock if it * exists. */ export function getDocblock(path: NodePath, trailing = false): string | null { let comments: CommentKind[] = []; if (trailing && path.node.trailingComments) { comments = path.node.trailingComments.filter( comment => comment.type === 'CommentBlock' && DOCBLOCK_HEADER.test(comment.value), ); } else if (path.node.leadingComments) { comments = path.node.leadingComments.filter( comment => comment.type === 'CommentBlock' && DOCBLOCK_HEADER.test(comment.value), ); } else if (path.node.comments) { comments = path.node.comments.filter( comment => comment.leading && comment.type === 'CommentBlock' && DOCBLOCK_HEADER.test(comment.value), ); } if (comments.length > 0) { return parseDocblock(comments[comments.length - 1].value); } return null; } /** * Given a string, this functions returns an object with doclet names as keys * and their "content" as values. */ export function getDoclets(str: string): Record<string, unknown> { const doclets = Object.create(null); let match = DOCLET_PATTERN.exec(str); for (; match; match = DOCLET_PATTERN.exec(str)) { doclets[match[1]] = match[2] || true; } return doclets; }