-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
780addf
commit e0e1852
Showing
6 changed files
with
424 additions
and
2 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import * as fs from "fs" | ||
import * as path from "path" | ||
import * as ts from "typescript" | ||
import { generateDocumentation, DocEntry } from "./parser" | ||
|
||
export default function docsAPI (base: string = '.', out: string, files: string[]) { | ||
const cwd: string = process.cwd(); | ||
const basepath: string = path.resolve(cwd, base); | ||
files.forEach(async s => { | ||
compile(cwd, s, (routepath, doc) => { | ||
console.log(routepath, doc.length) | ||
if (doc.length < 1) return | ||
const outpath: string = routepath | ||
.replace(basepath, path.resolve(cwd, out)) | ||
.replace(/(.[a-z]+)$|(.d.ts)$/ig, '') | ||
try { | ||
writeDoc(outpath, doc) | ||
} catch (error) { | ||
fs.mkdirSync(path.parse(outpath).dir, { recursive: true }) | ||
writeDoc(outpath, doc) | ||
} | ||
}) | ||
}) | ||
} | ||
|
||
export function compile (p: string, n: string, callback?: (routepath: string, doc: DocEntry[]) => void) { | ||
const route = path.resolve(p, n) | ||
const stat = fs.statSync(route) | ||
if (stat.isDirectory()) { | ||
fs.readdirSync(route, { | ||
encoding: 'utf8' | ||
}).forEach(filename => ![ | ||
'node_modules', 'bin', 'templates', 'dist', '__tests__', '__mocks__', '_book', '.vscode', '.idea' | ||
].includes(filename) && compile(route, filename, callback)) | ||
} else { | ||
const docTree = generateDocumentation(route, { | ||
target: ts.ScriptTarget.ES5, | ||
module: ts.ModuleKind.ESNext | ||
}) | ||
callback && callback(route, docTree) | ||
} | ||
} | ||
|
||
export function writeJson (routepath: string, doc: DocEntry[]) { | ||
fs.writeFileSync( | ||
`${routepath}.json`, | ||
JSON.stringify(doc, undefined, 4), | ||
{} | ||
) | ||
} | ||
|
||
export function writeDoc (routepath: string, doc: DocEntry[]) { | ||
fs.writeFileSync( | ||
`${routepath}.md`, | ||
JSON.stringify(doc, undefined, 4), | ||
{} | ||
) | ||
} | ||
|
||
// docsAPI('.', process.argv[2], process.argv.slice(3)) | ||
docsAPI('./packages/taro/types/api', 'api', ['./packages/taro/types/api/']) |
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,134 @@ | ||
import * as ts from "typescript" | ||
|
||
export interface DocEntry { | ||
name?: string | ts.__String | ||
fileName?: string | ||
documentation?: string | ||
jsTags?: ts.JSDocTagInfo[] | ||
type?: string | ||
constructors?: DocEntry[] | ||
parameters?: DocEntry[] | ||
returnType?: string | ||
members?: DocEntry[] | ||
exports?: DocEntry[] | ||
} | ||
|
||
export function generateDocumentation( | ||
filepath: string, | ||
options: ts.CompilerOptions | ||
): DocEntry[] { | ||
const program = ts.createProgram([filepath], options) | ||
const checker = program.getTypeChecker() | ||
|
||
const output: DocEntry[] = [] | ||
|
||
for (const sourceFile of program.getSourceFiles()) { | ||
// if (!sourceFile.isDeclarationFile) {} | ||
if (filepath === sourceFile.fileName) { | ||
ts.forEachChild(sourceFile, visitAST) | ||
} | ||
} | ||
|
||
return output | ||
|
||
function visitAST(node: ts.Node) { | ||
// Only consider exported nodes | ||
if (!isNodeExported(node as ts.Declaration) || node.kind === ts.SyntaxKind.EndOfFileToken || node.kind === ts.SyntaxKind.DeclareKeyword | ||
|| ts.isImportDeclaration(node) || ts.isImportEqualsDeclaration(node) || ts.isImportClause(node) | ||
|| ts.isExportAssignment(node) || ts.isExportDeclaration(node) | ||
|| ts.isExpressionStatement(node) || ts.isEmptyStatement(node) | ||
|| node.kind === ts.SyntaxKind.ExportKeyword) { | ||
return | ||
} | ||
|
||
if (ts.isVariableDeclaration(node) || ts.isClassDeclaration(node) && node.name) { | ||
const symbol = checker.getSymbolAtLocation(node) | ||
symbol && output.push(serializeClass(symbol)) | ||
} else if (ts.isFunctionDeclaration(node)) { | ||
const signature = checker.getSignatureFromDeclaration(node) | ||
signature && output.push(serializeSignature(signature)) | ||
} else if (ts.isInterfaceDeclaration(node)) { | ||
const symbol = checker.getTypeAtLocation(node).getSymbol() | ||
symbol && output.push(serializeType(symbol, undefined, 'InterfaceDeclaration')) | ||
} else if (ts.isTypeAliasDeclaration(node)) { | ||
const symbol = checker.getTypeAtLocation(node).getSymbol() | ||
symbol && output.push(serializeType(symbol, ts.idText(node.name), 'TypeAliasDeclaration')) | ||
} else if (ts.isEnumDeclaration(node)) { | ||
const symbol = checker.getTypeAtLocation(node).getSymbol() | ||
symbol && output.push(serializeType(symbol)) | ||
} else if (ts.isIdentifier(node)) { | ||
const symbol = checker.getTypeAtLocation(node).getSymbol() | ||
symbol && output.push(serializeType(symbol)) | ||
} else if (ts.isModuleDeclaration(node) || ts.isModuleBlock(node) || ts.isVariableStatement(node)) { | ||
// This is a namespace, visitAST its children | ||
ts.forEachChild(node, visitAST) | ||
} else if (ts.isVariableDeclarationList(node)) { | ||
node.declarations.forEach(d => { | ||
const symbol = d['symbol'] | ||
symbol && output.push(serializeType(symbol)) | ||
}) | ||
} else { | ||
console.log(`WARN: Statement kind ${node.kind} is missing parse!\n\n${node.getText()}`) | ||
} | ||
} | ||
|
||
/** Serialize a symbol into a json object */ | ||
function serializeSymbol(symbol: ts.Symbol, name?: string, type?: string): DocEntry { | ||
return { | ||
jsTags: symbol.getJsDocTags(), | ||
name: name || symbol.getName(), | ||
documentation: ts.displayPartsToString(symbol.getDocumentationComment(checker)), | ||
type: type || checker.typeToString( | ||
checker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration!) | ||
) | ||
} | ||
} | ||
|
||
/** Serialize a class symbol information */ | ||
function serializeClass(symbol: ts.Symbol) { | ||
const details = serializeSymbol(symbol) | ||
// Get the construct signatures | ||
const constructorType = checker.getTypeOfSymbolAtLocation( | ||
symbol, | ||
symbol.valueDeclaration! | ||
) | ||
const signatures = constructorType.getConstructSignatures() | ||
details.constructors = signatures.map(serializeSignature) | ||
return details | ||
} | ||
|
||
/** Serialize a types (type or interface) symbol information */ | ||
function serializeType(symbol: ts.Symbol, name?: string, type?: keyof typeof ts.SyntaxKind): DocEntry { | ||
// console.log(type, Object.keys(symbol)) | ||
const doc: DocEntry = serializeSymbol(symbol, name, type) | ||
symbol.exports && symbol.exports.forEach((value) => { | ||
if (!doc.exports) doc.exports = [] | ||
doc.exports.push(serializeSymbol(value)) | ||
}) | ||
symbol.members && symbol.members.forEach((value) => { | ||
if (!doc.members) doc.members = [] | ||
doc.members.push(serializeSymbol(value)) | ||
}) | ||
return doc | ||
} | ||
|
||
/** Serialize a signature (call or construct) */ | ||
function serializeSignature(signature: ts.Signature) { | ||
const typeParameters = signature.getTypeParameters() || [] | ||
return { | ||
jsTags: signature.getJsDocTags(), | ||
documentation: ts.displayPartsToString(signature.getDocumentationComment(checker)), | ||
parameters: signature.getParameters().map((e, i) => | ||
serializeSymbol(e, undefined, typeParameters[i] && checker.typeToString(typeParameters[i]))), | ||
returnType: checker.typeToString(signature.getReturnType()) | ||
} | ||
} | ||
|
||
/** True if this is visible outside this file, false otherwise */ | ||
function isNodeExported(node: ts.Declaration): boolean { | ||
return ( | ||
(ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Export) !== 0 || | ||
(!!node.parent/* && node.parent.kind === ts.SyntaxKind.SourceFile */) | ||
) | ||
} | ||
} |
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,178 @@ | ||
/** | ||
* Documentation for C | ||
*/ | ||
class C { | ||
/** | ||
* constructor documentation | ||
* @param a my parameter documentation | ||
* @param b another parameter documentation | ||
*/ | ||
constructor(a: string, b: C) { } | ||
} | ||
|
||
/** | ||
* for test | ||
*/ | ||
export interface test { | ||
a: string; | ||
// test doc | ||
b: InnerAudioContext; | ||
} | ||
|
||
type InnerAudioContext = { | ||
/** | ||
* 音频资源的地址,用于直接播放。2.2.3 开始支持云文件ID | ||
*/ | ||
src: HTMLMediaElement['src'] | ||
// 开始播放的位置(单位:s),默认为 0 | ||
startTime: number | ||
// 是否自动开始播放,默认为 false | ||
autoplay: HTMLMediaElement['autoplay'] | ||
// 是否循环播放,默认为 false | ||
loop: HTMLMediaElement['loop'] | ||
// 是否遵循系统静音开关,默认为 true。当此参数为 false 时,即使用户打开了静音开关,也能继续发出声音。从 2.3.0 版本开始此参数不生效,使用 wx.setInnerAudioOption 接口统一设置。 | ||
obeyMuteSwitch: boolean | ||
// 音量。范围 0~1。默认为 1 | ||
volume: HTMLMediaElement['volume'] | ||
// 当前音频的长度(单位 s)。只有在当前有合法的 src 时返回(只读) | ||
duration: HTMLMediaElement['duration'] | ||
// 当前音频的播放位置(单位 s)。只有在当前有合法的 src 时返回,时间保留小数点后 6 位(只读) | ||
currentTime: HTMLMediaElement['currentTime'] | ||
// 当前是是否暂停或停止状态(只读) | ||
paused: HTMLMediaElement['paused'] | ||
// 音频缓冲的时间点,仅保证当前播放时间点到此时间点内容已缓冲(只读) | ||
buffered: HTMLMediaElement["buffered"] | ||
// 播放 | ||
play: HTMLAudioElement["play"] | ||
// 暂停。暂停后的音频再播放会从暂停处开始播放 | ||
pause: HTMLAudioElement["pause"] | ||
// 停止。停止后的音频再播放会从头开始播放。 | ||
stop: () => void | ||
// 跳转到指定位置 | ||
seek: (position: number) => void | ||
/** | ||
* 销毁当前实例 | ||
*/ | ||
destroy: () => void | ||
// {(callback: function) => void} offCanplay(function callback) 取消监听音频进入可以播放状态的事件 | ||
// {(callback: function) => void} offEnded(function callback) 取消监听音频自然播放至结束的事件 | ||
// {(callback: function) => void} offError(function callback) 取消监听音频播放错误事件 | ||
// {(callback: function) => void} offPause(function callback) 取消监听音频暂停事件 | ||
// {(callback: function) => void} offPlay(function callback) 取消监听音频播放事件 | ||
// {(callback: function) => void} offSeeked(function callback) 取消监听音频完成跳转操作的事件 | ||
// {(callback: function) => void} offSeeking(function callback) 取消监听音频进行跳转操作的事件 | ||
// {(callback: function) => void} offStop(function callback) 取消监听音频停止事件 | ||
// {(callback: function) => void} offTimeUpdate(function callback) 取消监听音频播放进度更新事件 | ||
// {(callback: function) => void} offWaiting(function callback) 取消监听音频加载中事件 | ||
// {(callback: function) => void} onCanplay(function callback) 监听音频进入可以播放状态的事件。但不保证后面可以流畅播放 | ||
// {(callback: function) => void} onEnded(function callback) 监听音频自然播放至结束的事件 | ||
// {(callback: function) => void} onError(function callback) 监听音频播放错误事件 | ||
// {(callback: function) => void} onPause(function callback) 监听音频暂停事件 | ||
// {(callback: function) => void} onPlay(function callback) 监听音频播放事件 | ||
// {(callback: function) => void} onSeeked(function callback) 监听音频完成跳转操作的事件 | ||
// {(callback: function) => void} onSeeking(function callback) 监听音频进行跳转操作的事件 | ||
// {(callback: function) => void} onStop(function callback) 监听音频停止事件 | ||
// {(callback: function) => void} onTimeUpdate(function callback) 监听音频播放进度更新事件 | ||
// {(callback: function) => void} onWaiting(function callback) 监听音频加载中事件。当音频因为数据不足,需要停下来加载时会触发 | ||
} | ||
|
||
/** | ||
* 创建内部 audio 上下文 InnerAudioContext 对象。 | ||
*/ | ||
export const createInnerAudioContext = (): InnerAudioContext => { | ||
let audioEl: HTMLAudioElement = new Audio() | ||
|
||
const iac: InnerAudioContext = { | ||
src: audioEl.src, | ||
startTime: 0, | ||
autoplay: audioEl.autoplay, | ||
loop: audioEl.loop, | ||
obeyMuteSwitch: false, | ||
volume: audioEl.volume, | ||
duration: audioEl.duration, | ||
currentTime: audioEl.currentTime, | ||
buffered: audioEl.buffered, | ||
paused: audioEl.paused, | ||
play: () => audioEl.play(), | ||
pause: () => audioEl.pause(), | ||
stop: () => { | ||
iac.pause() | ||
iac.seek(0) | ||
}, | ||
seek: position => { | ||
audioEl.currentTime = position | ||
}, | ||
/** | ||
* @todo destroy 得并不干净 | ||
*/ | ||
destroy: () => { | ||
iac.stop() | ||
document.body.removeChild(audioEl) | ||
audioEl = null | ||
} | ||
} | ||
|
||
const simpleProperties = [ 'src', 'autoplay', 'loop', 'volume', 'duration', 'currentTime', 'buffered', 'paused' ] | ||
simpleProperties.forEach(propertyName => { | ||
Object.defineProperty(iac, propertyName, { | ||
get: () => audioEl[propertyName], | ||
set (value) { audioEl[propertyName] = value } | ||
}) | ||
}) | ||
|
||
const simpleEvents = [ | ||
'Canplay', | ||
'Ended', | ||
'Pause', | ||
'Play', | ||
'Seeked', | ||
'Seeking', | ||
'TimeUpdate', | ||
'Waiting' | ||
] | ||
const simpleListenerTuples = [ | ||
['on', audioEl.addEventListener], | ||
['off', audioEl.removeEventListener] | ||
] | ||
|
||
simpleEvents.forEach(eventName => { | ||
simpleListenerTuples.forEach(([eventNamePrefix, listenerFunc]: any) => { | ||
Object.defineProperty(iac, `${eventNamePrefix}${eventName}`, { | ||
get () { | ||
return callback => listenerFunc.call(audioEl, eventName.toLowerCase(), callback) | ||
} | ||
}) | ||
}) | ||
}) | ||
|
||
const customEvents = [ 'Stop', 'Error' ] | ||
const customListenerTuples = [ | ||
['on', 'add'], | ||
['off', 'remove'] | ||
] | ||
|
||
customEvents.forEach(eventName => { | ||
customListenerTuples.forEach(([eventNamePrefix, actionName]) => { | ||
Object.defineProperty(iac, `${eventNamePrefix}${eventName}`, { | ||
get () {} | ||
}) | ||
}) | ||
}) | ||
|
||
return iac | ||
} | ||
|
||
/** | ||
* @typedef {object} AudioContext | ||
* @property {(src: string) => void} setSrc 设置音频地址 | ||
* @property {() => void} play 播放音频。 | ||
* @property {() => void} pause 暂停音频。 | ||
* @property {(position: number) => void} seek(number position) 跳转到指定位置。 | ||
*/ | ||
|
||
/** | ||
* 创建 audio 上下文 AudioContext 对象。 | ||
* @param {string} id <audio> 组件的 id | ||
* @param {Object} this 在自定义组件下,当前组件实例的this,以操作组件内 <audio> 组件 | ||
* @returns {AudioContext} | ||
*/ |
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,6 @@ | ||
{ | ||
"extends": "../tsconfig.json", | ||
"compilerOptions": { | ||
"module": "umd" | ||
} | ||
} |
Oops, something went wrong.