-
Notifications
You must be signed in to change notification settings - Fork 98
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: 自动化生成文档属性说明
- Loading branch information
Showing
5 changed files
with
614 additions
and
39 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, ' ') | ||
} | ||
if (con && k === 'value') { | ||
con = `_${con}_` | ||
} else if (con && k === 'require') { | ||
con = '`' + con + '`' | ||
} else if (!con) { | ||
con = `-` | ||
} | ||
mdRes += `| ` + `${con} `.replace(/\|/g, '¦') | ||
} | ||
}) | ||
|
||
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
|
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.