npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@esydoc/doclet-parser

v2.1.3

Published

The parser for doclet that it resolved by jsdoc

Downloads

65

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

DocletAST 最大区别在于,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'