Skip to content

Commit

Permalink
feat: ts to md (#68)
Browse files Browse the repository at this point in the history
* feat: 自动化生成文档属性说明
  • Loading branch information
zuolung authored Nov 17, 2021
1 parent 404741a commit e6f21fb
Show file tree
Hide file tree
Showing 5 changed files with 614 additions and 39 deletions.
9 changes: 6 additions & 3 deletions packages/vantui-doc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
"version": "1.2.2",
"description": "",
"scripts": {
"dev": "node synch.js && npx vant-cli dev",
"dev": "node ./scripts/ts-to-md && npx vant-cli dev",
"test:coverage": "open test/coverage/index.html",
"build": "node synch.js && sh ./build.sh",
"build": "node ./scripts/ts-to-md && sh ./build.sh",
"release": "npx gh-pages -d site",
"docs-ts": "node ./synch.js"
"docs-ts": "node ./scripts/ts-to-md"
},
"author": "sanshao",
"license": "MIT",
Expand All @@ -18,8 +18,11 @@
"devDependencies": {
"@vant/cli": "^3.9.0",
"@vue/compiler-sfc": "^3.0.0",
"ast-to-markdown": "^1.0.0",
"cross-env": "^7.0.3",
"glob": "^7.2.0",
"markdown-to-ast": "^6.0.3",
"prettier": "^2.4.1",
"vue": "^3.0.0"
},
"browserslist": [
Expand Down
137 changes: 137 additions & 0 deletions packages/vantui-doc/scripts/ts-to-md.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
const fs = require('fs')
const path = require('path')
const glob = require('glob')
const parser = require('./utils/ts-parser')
const markdownToAst = require('markdown-to-ast')
const astToMarkdown = require('ast-to-markdown')
const ora = require('../node_modules/ora/index')

const GITHUB_TYPESHS = `https://github.com/AntmJS/vantui/tree/main/packages/vantui/types`
const READMES_PATH = `${path.resolve(process.cwd(), './src/**/README.md')}`
const spinner = ora(`文档 API 同步开始`)

glob(READMES_PATH,
function (err, path_) {
path_.map((item) => {
const componentName = item.split('/').reverse()[1]
let content = fs.readFileSync(item, 'utf-8')
spinner.start(`${componentName}文档 API 同步中...`)

if (content) {
content = removeOldTable(content)
}

if (
fs.existsSync(`../vantui/types/${componentName}.d.ts`) &&
componentName !== 'index'
) {
let tsInfo = fs.readFileSync(
`../vantui/types/${componentName}.d.ts`,
'utf-8',
)
const res = parser(tsInfo)

fs.writeFileSync(item, content + createMd(res, componentName))
spinner.stop(`${componentName}文档 API 同步完成`)
}
})

spinner.succeed(`文档 API 同步完成`)
},
)

function createMd(obj, compName) {
let mdRes = ``
for (const Dkey in obj) {
const item = obj[Dkey]
if (!Object.keys(item).length) continue
mdRes +=
`### ${item['title'] && typeof item['title'] === 'string'
? item['title']
: Dkey
}` +
` [[详情]](${GITHUB_TYPESHS}/${compName}.d.ts)
`
mdRes += `${item['description'] || ''}
`
let header = `| 参数 | 说明 | 类型 | 默认值 | 必填 |
| --- | --- | --- | --- | --- |
`
let key = ['self', 'description', 'value', 'default', 'require']
if (!Dkey.includes('Props')) {
header = `| 参数 | 说明 | 类型 |
| --- | --- | --- |
`
key = ['self', 'description', 'value']
}

if (Dkey.includes('Instance')) {
header = `| 方法 | 说明 | 类型 |
| --- | --- | --- |
`
key = ['self', 'description', 'value']
}
mdRes += header
Object.keys(item).map((_key) => {
if (typeof item[_key] === 'object' && item[_key]) {
key.forEach((k) => {
if (k === 'self') {
mdRes += `| ${_key} `
} else {
let con = item[_key][k]
if (k === 'value') {
con = con
.replace(/[\n]+/g, '<br/>')
.replace(/\s(?!=\/)/g, '&nbsp;')
}
if (con && k === 'value') {
con = `_${con}_`
} else if (con && k === 'require') {
con = '`' + con + '`'
} else if (!con) {
con = `-`
}
mdRes += `| ` + `${con} `.replace(/\|/g, '&brvbar;')
}
})

mdRes += `|
`
}
})
mdRes += `\n`
}
return mdRes
}

function removeOldTable(md) {
let ast = markdownToAst.parse(md)
let shouldRmoveIndex
let firstTableIndex
ast.children.forEach((as, index) => {
if (as.type === 'Table' && firstTableIndex === undefined) {
firstTableIndex = index
}
})

for (let index = firstTableIndex; index >= 0; index--) {
if (ast.children[index].type === 'Header') {
shouldRmoveIndex = index
break
}
}
// 处理第一版ts的展示
if (shouldRmoveIndex === undefined) {
ast.children.forEach((as, index) => {
if (as.type === 'Header' &&
as.raw === '### TS信息' &&
shouldRmoveIndex === undefined
) {
shouldRmoveIndex = index
}
})
}

ast.children = ast.children.slice(0, shouldRmoveIndex)
return astToMarkdown(ast)
}
203 changes: 203 additions & 0 deletions packages/vantui-doc/scripts/utils/ts-parser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
const Prettier = require('prettier')
/** 收集d.ts暴露第一层级的属性和注释 */
module.exports = function main(tsStr) {
tsStr = Prettier.format(tsStr, { semi: false, parser: 'typescript' })
const { TOKENS } = getAllTokens(tsStr)
const res = parseTokens(TOKENS)

return res
}

function getAllTokens(str) {
let TOKENS = []
let token = ''
for (let i = 0; i < str.length; i++) {
let curItem = str.charAt(i)
if (curItem === ' ' || curItem === '\n') {
if (token) TOKENS.push(token.replace(/[\s]+/, ''))
TOKENS.push(curItem)
token = ''
} else {
token += curItem
}
}

TOKENS = TOKENS.slice(TOKENS.indexOf('export'), TOKENS.length)
TOKENS = TOKENS.slice(0, TOKENS.indexOf('declare'))

return { TOKENS }
}

function parseTokens(TOKENS) {
const res = {}
let value = ''
let count = 0
let exportName = ''
let attrName = ''
let attrNamePrev = ''
let InBrackets = 0
let commentsPenddingStr = ''
let commentsStr = ''
let STATUS = {
EXPORT_1: 'EXPORT_WHAT',
EXPORT_2: 'EXPORT_GET_WHAT',
EXPORT_3: 'EXPORTING',
}
let status = STATUS.EXPORT_1

for (let i = 0; i < TOKENS.length; i++) {
let curToken = TOKENS[i]
let nextToken = getRealTokenNext(1, TOKENS.slice(i + 1, TOKENS.length - 1))
let next2Token = getRealTokenNext(2, TOKENS.slice(i + 1, TOKENS.length - 1))

if (curToken === '/**' || commentsPenddingStr) {
commentsPenddingStr += curToken
}

if (curToken === 'export' && ['interface', 'type'].includes(nextToken)) {
if (!res[next2Token]) {
exportName = next2Token
res[exportName] = {
...parseComments(commentsStr),
}
commentsStr = ''
}
status = STATUS.EXPORT_2
}

if (curToken.includes('}') || curToken.includes('})')) {
count--
if (count === 0) {
status = STATUS.EXPORT_1
if (attrName && exportName) res[exportName][attrName]['value'] = formatTsValue(value)
value = ''
attrNamePrev = ''
attrName = ''
InBrackets = 0
exportName = ''
}
}

if (exportName && count !== 0) {
if (
curToken.includes(':') &&
count < 2 &&
!curToken.includes('(') &&
InBrackets === 0
) {
attrNamePrev = attrName
attrName = curToken.replace(':', '').replace('?', '')
if (!res[exportName][attrName]) {
res[exportName][attrName] = {
...parseComments(commentsStr),
}
commentsStr = ''
}
if (curToken.includes('?')) {
res[exportName][attrName]['require'] = 'false'
} else {
res[exportName][attrName]['require'] = 'true'
}
if (attrNamePrev) {
if (!res[exportName]) res[exportName] = {}
if (!res[exportName][attrNamePrev]) res[exportName][attrNamePrev] = {}
res[exportName][attrNamePrev]['value'] = formatTsValue(value)
value = ''
}
} else if (status === STATUS.EXPORT_3 && !commentsPenddingStr) {
value += `${curToken}`
}
}
if (curToken === '*/') {
commentsStr = commentsPenddingStr + curToken
commentsPenddingStr = ''
}
const InBracketsNum1 = countStrNumInToken('(', curToken)
const InBracketsNum2 = countStrNumInToken(')', curToken)

if (InBracketsNum1 > 0) {
InBrackets += InBracketsNum1
}
if (InBracketsNum2) {
InBrackets -= InBracketsNum2
}

if (curToken.includes('{') && status === STATUS.EXPORT_2) {
status = STATUS.EXPORT_3
}

if (curToken.includes('{') || curToken.includes('({')) {
count++
}
}

return res
}

function countStrNumInToken(str, token) {
let res = 0
for (let i = 0; i < token.length; i++) {
if (token.charAt(i) === str) {
res++
}
}

return res
}

function getRealTokenNext(nextNum, lastTokens) {
let res = ''
let next_i = 0
for (let a = 0; a < lastTokens.length; a++) {
if (lastTokens[a] !== ' ' && lastTokens[a] !== '\n') {
next_i++
if (next_i === nextNum) {
res = lastTokens[a]
break
}
}
}

return res
}

function parseComments(comments = '') {
let res = {}
const arr = comments
.split('\n')
.filter((item) => item.includes('@'))
.map((item) => item.replace(/^[\s]+/g, ''))
.map((item) => item.replace('* ', ''))
.map((item) => item.replace('@', ''))
.map((item) => item.replace(/[\s]+/, '##'))

arr.forEach((item) => {
const cons = item.split('##')
res[cons[0]] = cons[1]
})

return res
}

function formatTsValue(tsValue) {
let res = Prettier.format(
`type temp = {
attr:${tsValue}
}
// END`,
{
parser: 'typescript',
semi: false,
printWidth: 48,
},
).replace('type temp = {\n', '')
.replace('attr: ', '')
.replace(
`}
// END`,
'',
)

return res
}

29 changes: 0 additions & 29 deletions packages/vantui-doc/synch.js

This file was deleted.

Loading

0 comments on commit e6f21fb

Please sign in to comment.