接口文档=>ts代码

接口文档=>ts代码,不能完美转化,但是还比较好用

目前为 2021-09-09 提交的版本。查看 最新版本

// ==UserScript==
// @name         接口文档=>ts代码
// @namespace    http://tampermonkey.net/
// @version      0.1.2
// @description  接口文档=>ts代码,不能完美转化,但是还比较好用
// @author       fangxianli
// @match        https://km.sankuai.com/*
// @icon         https://www.google.com/s2/favicons?domain=undefined.
// @grant        none
// ==/UserScript==

(function() {
    'use strict';
const InterfaceModel = {
  names: [/参数名/, /名称/, /参数/, /字段名/],
  types: [/类型/],
  means: [/含义/, /意义/, /意思/, /描述/, /解释/],
  examples: [/示例/, /例子/]
}

// TODO: 做成可配置的
const TYPE_MAP = [
  {
    name: 'number',
    match: [
      /number/i,
      /int/i,
      /数字/,
      /integer/i,
      /float/i,
      /时间/,
      /date/i,
      /整数/,
      /整型/
    ]
  },
  {
    name: 'string',
    match: [
      /string/i,
      /字符/,
      /字符串/,
      // 长整型建议使用字符串
      /long/i,
      /长整型/
    ]
  },
  {
    name: 'boolean',
    match: [
      /Boolean/i,
      /布尔/,
    ]
  },
  {
    name: 'array',
    match: [
      /list<(\w+)>/i
    ]
  },
  {
    name: 'object',
    match: [
      /(\w+)/
    ]
  }
]

/**
 * 获取之前最邻近的文本内容
 * @param {Element} $currentNode 
 */
const findPrevText = ($currentNode) => {
  if (!$currentNode) return
  if ($currentNode?.previousElementSibling?.innerText) return $currentNode?.previousElementSibling?.innerText
  return findPrevText($currentNode.parentElement)
}

/**
 * 深度优先遍历节点,获取树上最深的第一个文本节点
 * @param {Element} node 
 */
const dfsFindText = (node) => {
  if (!node?.innerText) return
  if (!node?.children.length) return node.innerText
  for (let i = 0; i < node.children.length; i++) {
    const text = dfsFindText(node.children[i])
    if (text !== undefined) return text
  }
}

/**
 * 
 * @param {Element} element 
 */
const getContent = (element) => {
  if (!element) return
  const tableInTD = element.querySelectorAll('table')[0]
  if (tableInTD) return findPrevText(tableInTD)
  return element?.innerText?.trim()
}

const testInRegExpArray = (testStr, regExpArr = []) => regExpArr.some(regExp => regExp.test(testStr))

/**
 * 从表头里获取对应字段的位置
 * @param {String} name 字段名
 * @param {Element[]} headers 表头
 */
const getPropsIndex = (name, headers) => headers.findIndex(header => testInRegExpArray(header.innerText, InterfaceModel[name]))

/**
 * 将 table 中的数据转化为能够被识别的类型
 * @param {Element} table 
 */
const convertTable2Map = (table) => {
  const prevText = findPrevText(table) || ''
  const charStringArray = prevText?.match(/\w+/g)
  const interfaceName = charStringArray ? charStringArray.join('-') : 'UnknownInterface'
  const typeModel = {
    name: interfaceName,
    defineList: [],
    table
  }
  const headers = [...table.querySelectorAll('th')]
  const nameIndex = getPropsIndex('names', headers)
  const typeIndex = getPropsIndex('types', headers)
  const meanIndex = getPropsIndex('means', headers)
  const exampleIndex = getPropsIndex('examples', headers)
  // 找到第一个 display 不为 none 的 body,即为数据表
  const targetTable = [...table.querySelectorAll('tbody')].find(tbody => tbody.style.display !== 'none')
  // 去除表头
  const rows = [...targetTable.children]
  rows.splice(0, 1)
  const defineList = rows.map((row) => {
    return {
    name: getContent(row.children[nameIndex]),
    type: getContent(row.children[typeIndex]),
    mean: getContent(row.children[meanIndex]),
    example: getContent(row.children[exampleIndex]),
  }})

  typeModel.defineList = defineList
  return typeModel
}

const getComment = ({mean, example}) => {
  if (!mean && !example) return []
  const comment = [
    `/**`,
    ` * ${mean}`,
    ` */`
  ]
  // 如果有注释的话添加注释
  if (example) comment.splice(1, 0, ` * 示例:${example}`)
  return comment
}

const getTSTypeStr = (type) => {
  if (!type) return 'unknown'
  const tsTypeDesc = TYPE_MAP.find(typeMap => {
    return testInRegExpArray(type, typeMap.match)
  })
  const directReturnArr = ['number', 'string', 'boolean']
  const canDirectReturnName = directReturnArr.includes(tsTypeDesc?.name)
  const needMatch = tsTypeDesc?.name === 'array' || tsTypeDesc?.name === 'object'
  if (canDirectReturnName) return tsTypeDesc?.name
  if (needMatch) {
    for (const match of tsTypeDesc.match) {
      const matchResult = type.match(match)
      if (!matchResult?.[1]) continue
      if (tsTypeDesc.name === 'array') return matchResult[1] + '[]'
      return matchResult[1]
    }
  }
  return 'unknown'
}

const getNameDefine = ({name, type}) => {
  const typeStr = getTSTypeStr(type)
  return [`${name}: ${typeStr}`]
}

const addTab = str => `  ${str}`

const convertData2Code = ({name, defineList}) => {
  const getCode = (defineItem) => {
    return [...getComment(defineItem), ...getNameDefine(defineItem)]
  }
  
  const interfaceDefine = [
    `interface ${name} {`,
    ...defineList.map(getCode).flat(Infinity).map(addTab),
    `}`,
    ''
  ]
  return interfaceDefine
}

const getCode = () => {
  const allTables = document.querySelectorAll('table')
  /**
   * 筛选出接口定义的table
   */
  const defineTable = [...allTables].filter(table => {
    const headers = [...(table.querySelector('tr')?.children || [])]
    const tableRows = table.querySelectorAll('tr')
    const hasNameTableHeader = headers.some(header => testInRegExpArray(header.innerText, InterfaceModel.names))
    const hasMeanTableHeader = headers.some(header => testInRegExpArray(header.innerText, InterfaceModel.means))
    return hasNameTableHeader && hasMeanTableHeader && tableRows.length > 1
  })

  const defineDataList = defineTable.map(convertTable2Map)
  console.log({defineDataList})
  const codeArray = defineDataList.map(convertData2Code)
  console.log(codeArray)
  return codeArray
}

const copyCode = () => {
  const codeArray = getCode()
  const textArea = document.createElement('textarea')
  textArea.value = codeArray.flat(Infinity).join('\n')
  document.body.append(textArea)
  textArea.select()
  document.execCommand('copy')
  // document.body.remove(textArea)
  textArea.style.display = 'none'
  alert('转换代码复制成功')
}

document.addEventListener('keypress', (event) => {
  console.log(event)
  if (event.code === 'KeyJ' && event.shiftKey && event.altKey && event.ctrlKey) {
    copyCode()
  }
})

// Your code here...
})();