@esydoc/doclet-parser
v2.1.3
Published
The parser for doclet that it resolved by jsdoc
Downloads
126
Readme
@esydoc/doclet-parser
Take the doclet root to parse be a whole AST.
Doclet
What is the doclet?
可以理解它为一个对注释的一个抽象
,通常 babel 解析 JS AST 的时候,是不会处理 comment 节点(它只是个字符串),
而jsdoc
则利用 babel 提供的 hooks, 拿到 comment 再进行二次解析,转成 doclet 对象。
Doclet vs AST
Doclet
与 AST
最大区别在于,Doclet
节点都是属于离散的节点,它只保留子树的 key,而不是子树,这就意味着它并不是一颗完整的 AST,
而我们渲染数据的时候却需要, 这时候需要 doclet-parser
为我们处理这一难题。
举例子:
/**
* 小程序入口控制参数
* @edata
* @typedef {Object} EntranceReq
* @property {string} extType
* @property {boolean} visible 入口是否显示
* @property {Object} [param] 自定义参数
*/
/**
* 小程序入口控制
* @eapi
* @param {EntranceReq} params 输入参数
* @returns {Promise<any>}
*/
localControlEntrance(params) {
return extsdk.core.callEvent(MODULE_NAME, 'localControlEntrance', params);
},
转换后的 api doclet 是这样的:
// 伪代码 doclet对象
const apiDoclet = {
name: 'localControlEntrance',
params: [
{
type: {
names: ['EntranceReq'] // 这里这是一个key, 标记引用的节点,而不是实际的AST
},
name: 'params'
}
],
....
}
const EntranceReqDoclect = {
name: EntranceReq,
type: {
names: ['Object']
}
properties: [
{
name: 'extType',
types: {
{
names: ['string']
}
}
},
...
]
}
经过 doclet-parser 处理之后:
// params 转抽象为一个 root 节点
const root = {
name: 'root',
type: 'Array',
properties: [
{
name: 'params',
type: 'Object',
properties: [
{
name: 'extType',
type: 'string'
},
...
]
}
]
}
看到转换后的节点我们发现离散的 doclet 节点都被适当地抽象融合到了一个颗 root 树上,这样我们就可以使用 root 树去渲染各种数据啦~。
API
Constructor(fileDataMap: FileDataMap)
import DocletParser from '@esydoc/doclet-parser'
export type DataMap = Map<string, RawDataTreeNode> // RawDataTreeNode 其实就是 doclet
export type FileDataMap = Map<string | symbol, DataMap> // key 代表的是文件路径,用来收集沿路`@edata`标记的数据节点
const fileDataMap = new Map() // 存有 doclet 信息的 map
const parser = new DocletParser(fileDataMap)
Instance API
parser.parse(apiDoclet, reflectConfig): ParsedResult
- 将 apiDoclet 节点转换为一颗完整 AST,其中 apiDoclet 是通过@eapi标签
标记的注释节点,而reflectConfig 配置是一个格式化 Doclet 字段的一个映射配置。
reflectConfig
介绍
export const ApiReflectConfig = Object.freeze({
args: {
key: 'args',
reflectKey: 'params',
format: (data: any) => {
return [data] // args是一个二维数组
},
nodeType: 'Array'
},
ret: {
key: 'ret',
reflectKey: 'returns',
format: (data: any) => {
return data // expect是一个一维数组
},
nodeType: 'Array'
}
})
如何获取上述默认配置?
import {
ApiReflectConfig,
} from '@esydoc/doclet-parser'
ParsedResult数据结构:
export type TransformedDataTreeNode = {
name: string
properties?: TransformedDataTreeNode[]
parent: TransformedDataTreeNode | null
description?: string
optional?: boolean
type: string
id: string
rawType?: string
}
type ParsedResult = {
[key: string]: TransformedDataTreeNode;
} | null
Helpers
节点相关操作
- traverseNode - 深度优先遍历节点(递归)
function traverseNode(
node: TransformedDataTreeNode, // root
onPush: (node: TransformedDataTreeNode) => void, // 访问节点回调
onPop: (node: TransformedDataTreeNode) => void // 访问节点回调
): void
- widthFirstTraverseNode - 广度优先遍历节点(循环)
虽然用递归的方式计算深度比较简单,但因为栈的原因,不能提前结束,所以采用循环比较合理
const widthFirstTraverseNode = (
node: DataTreeNode, // root
visit: (node: DataTreeNode, depth: number) => boolean | void // 访问节点回调
): void
- transofrmDataTreeNode2Value - 转换节点为数据值
function transofrmDataTreeNode2Value(node: DataTreeNode): any
以上文提到的 root 为例子:
const root = {
name: 'root',
type: 'Array',
properties: [
{
name: 'params',
type: 'Object',
properties: [
{
name: 'extType',
type: 'string'
},
...
]
}
]
}
const val = transofrmDataTreeNode2Value(root)
// 以下val是root实际生成的值
val = [
{
extType: ''
}
]
isDateTreeNode(node: DataTreeNode): boolean - 是否是 DataTreeNode
getFirstChild(node: DataTreeNode): DataTreeNode | null - 获取第一个子节点
isMutiValueNode(node: DataTreeNode): boolean - 该节点是否是一个多分支节点,例如:enum节点,polytype节点 (a|b|c)
unwrap(node: DataTreeNode): DataTreeNode - 提取 doclet root 实际有效的节点
doclet root 有两种形态:args, ret
/**
* @eapi
* @param {EntranceReq} params -> args
* @returns {Promise<any>} -> ret
*/
localControlEntrance(params) {
return extsdk.core.callEvent(MODULE_NAME, 'localControlEntrance', params);
}
从上述代码我们可以看出,@param
和 @returns
分别 转换为 doclet root 的 args 和 ret 两种形态。
此时,我们使用unwarp进行解包可以速度拿到关键数据:
const unwarpedArgs = unwrap(argsDocletRoot) // => got 'param(EntranceReq)' node
const unwarpedRet = unwrap(retDocletRoot) // => got 'any' node directly without promise & parent root
- clean - 删除树多余字段,优化序列化的速度,减少生成文件的代码量
type CleanRuleObj = {
key: keyof TransformedDataTreeNode,
assert: string | ((val: any) => boolean) // - true,删除该字段,反之亦然
}
type CleanRule = keyof TransformedDataTreeNode | CleanRuleObj
// 默认配置
const cleanDefaultRules: CleanRule[] = [
'description', // 直接传字符串,则直接删除
'parent',
'id',
{
key: 'properties',
assert: 'undefined' // assert 既可以是字符串,此时会默认通过
// typeof val === assert 判断字段是否删除,
// 也可以传一个((val: any) => boolean)回调,自己控制字段是否删除
},
{
key: 'optional',
assert: 'undefined'
},
{
key: 'rawType',
assert: 'undefined'
}
]
// declare
function clean(tree: TransformedDataTreeNode, rules: CleanRule[] = cleanDefaultRules): TransformedDataTreeNode
通用相关操作
- genUniId(prefix: string) - prefix 拼接一个 hash, 生成一个唯一id
引用方式
import {
genUniId,
clean,
unwrap,
getFirstChild,
isDateTreeNode,
transofrmDataTreeNode2Value,
widthFirstTraverseNode,
traverseNode
} from '@esydoc/doclet-parser'