UNPKG

@mpxjs/vuese-parser

Version:

Vue file parser for automatic document generation

1,329 lines (1,311 loc) 73.6 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var build = require('vue-template-compiler/build'); var parser$1 = require('@babel/parser'); var path = require('path'); var fs = require('fs'); var traverse = require('@babel/traverse'); var bt = require('@babel/types'); var generate = require('@babel/generator'); var Typedoc = require('typedoc'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } function _interopNamespace(e) { if (e && e.__esModule) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n["default"] = e; return Object.freeze(n); } var path__namespace = /*#__PURE__*/_interopNamespace(path); var fs__namespace = /*#__PURE__*/_interopNamespace(fs); var traverse__default = /*#__PURE__*/_interopDefaultLegacy(traverse); var bt__namespace = /*#__PURE__*/_interopNamespace(bt); var generate__default = /*#__PURE__*/_interopDefaultLegacy(generate); var Typedoc__default = /*#__PURE__*/_interopDefaultLegacy(Typedoc); // Use vue-template-compiler/build to avoid detection of vue versions function sfcToAST(source, babelParserPlugins, basedir) { const plugins = getBabelParserPlugins(babelParserPlugins); const sfc = build.parseComponent(source); const res = { jsFilePath: '', jsSource: '', templateSource: '', styleSource: '' }; if (sfc.script) { if (!sfc.script.content && sfc.script.src) { // Src Imports if (basedir) { res.jsFilePath = path__namespace.resolve(basedir, sfc.script.src); try { sfc.script.content = fs__namespace.readFileSync(path__namespace.resolve(basedir, sfc.script.src), 'utf-8'); } catch (e) { console.error(e); sfc.script.content = ''; } } } res.sourceType = sfc.script.lang || 'js'; res.jsSource = sfc.script.content || ''; res.jsAst = parser$1.parse(sfc.script.content, { sourceType: 'module', plugins }); } if (sfc.template) { if (!sfc.template.content && sfc.template.src) { // Src Imports if (basedir) { try { sfc.template.content = fs__namespace.readFileSync(path__namespace.resolve(basedir, sfc.template.src), 'utf-8'); } catch (e) { console.error(e); sfc.template.content = ''; } } } res.templateSource = sfc.template.content || ''; res.templateAst = build.compile(sfc.template.content, { comments: true }).ast; } if (sfc.styles.length > 0) { sfc.styles.map(style => { if (!style.content && style.src) { if (basedir) { try { res.styleSource += '\n' + fs__namespace.readFileSync(path__namespace.resolve(basedir, style.src), 'utf-8'); } catch (e) { console.error(e); } } } else { res.styleSource += '\n' + style.content; } }); } return res; } function getBabelParserPlugins(plugins) { const defaultBabelParserPlugins = { objectRestSpread: true, dynamicImport: true, 'decorators-legacy': true, classProperties: true, typescript: true, jsx: true }; const finallyBabelParserPlugins = Object.assign(defaultBabelParserPlugins, plugins || {}); return Object.keys(finallyBabelParserPlugins).filter((k) => finallyBabelParserPlugins[k]); } // type _ParserOptions = function scriptToAst(source, options) { const plugins = getBabelParserPlugins(options.babelParserPlugins); const res = { jsFilePath: '', jsSource: '', templateSource: '' }; res.sourceType = options.filepath ? options.filepath.slice(-2) : ''; res.jsFilePath = path__namespace.resolve(options.filepath || ''); res.jsSource = source; res.jsAst = parser$1.parse(source, { sourceType: 'module', plugins }); res.templateSource = ''; return res; } const commentRE = /\s*\*\s{1}/g; const leadRE = /^@(\w+)\b/; /** * @param cnode {bt.Node} a node with comments * @param trailing {boolean} Whether to process the tailing comment */ function getComments(cnode, trailing) { const res = { default: [] }; const commentNodes = trailing ? cnode.trailingComments || [] : cnode.leadingComments || []; if (!commentNodes || !commentNodes.length) return res; let comments = '', matchs, codeBlockStarted; commentNodes.forEach((node) => { if (isCommentLine(node)) { if (isCodeBlockDeclaration(node.value) && codeBlockStarted) codeBlockStarted = false; comments = codeBlockStarted ? node.value.replace(/^\s/, '') : node.value.trim(); if (isCodeBlockDeclaration(node.value) && typeof codeBlockStarted === 'undefined') codeBlockStarted = true; matchs = comments.match(leadRE); if (matchs) { const key = matchs[1]; res[key] = res[key] || []; res[key].push(comments.replace(leadRE, '').trim()); } else { res.default.push(comments); } } else if (isCommentBlock(node)) { comments = node.value .replace(commentRE, '\n') .replace(/^\*/, '') .split('\n'); comments = filterBlockComments(comments); let currentKey = 'default'; comments.forEach(c => { if ((matchs = c.match(leadRE))) { currentKey = matchs[1]; res[currentKey] = res[currentKey] || []; res[currentKey].push(c.replace(leadRE, '').trim()); } else { res.default.push(c); } }); } }); Object.keys(res).forEach(k => { res[k] = res[k].filter(comment => !comment.includes('eslint-disable')); }); return res; } /** * Extract the leading comments of the default export statement * 1、If the default export is a class with a decorator, * we should find the trailing comments of the last decorator node. * 2、In other cases, directly use the leading commets of the default export statement. */ function getComponentDescribe(node) { let res = { default: [] }; if (bt__namespace.isClassDeclaration(node.declaration)) { const decorators = node.declaration.decorators; if (decorators && decorators.length) { res = getComments(decorators[decorators.length - 1], true /* trailing */); } } else { res = getComments(node); } return res; } function isCommentLine(node) { return node.type === 'CommentLine'; } function isCommentBlock(node) { return node.type === 'CommentBlock'; } function isCodeBlockDeclaration(value) { return value.includes('```'); } function filterBlockComments(comments) { let codeBlockStarted; return comments .map(t => { if (isCodeBlockDeclaration(t) && codeBlockStarted) codeBlockStarted = false; const res = codeBlockStarted ? t : t.trim(); if (isCodeBlockDeclaration(t) && typeof codeBlockStarted === 'undefined') codeBlockStarted = true; return res; }) .filter(t => t); } /** * If a node satisfies the following conditions, then we will use this node as a Vue component. * 1. It is a default export * 2. others... */ function isVueComponent(path, componentLevel) { const node = path.node; return (bt__namespace.isExportDefaultDeclaration(node) || bt__namespace.isCallExpression(node) || bt__namespace.isVariableDeclarator(node) || (bt__namespace.isReturnStatement(node) && componentLevel === 1)); } function isValidObjectProperty(node) { return bt__namespace.isObjectProperty(node) || bt__namespace.isObjectMethod(node); } function isVueOption(path, optionsName, componentLevel) { const optionsNames = optionsName.split('|'); if (isValidObjectProperty(path.node) && path.parentPath && path.parentPath.parentPath && isVueComponent(path.parentPath.parentPath, componentLevel)) { // General component options return optionsNames.includes(path.node.key.name); } else if (isValidObjectProperty(path.node) && path.parentPath && path.parentPath.parentPath && bt__namespace.isCallExpression(path.parentPath.parentPath.node) && path.parentPath.parentPath.node.callee.name === 'Component' && path.parentPath.parentPath.parentPath && bt__namespace.isDecorator(path.parentPath.parentPath.parentPath.node)) { // options in ts @Component({...}) return optionsNames.includes(path.node.key.name); } return false; } function runFunction(fnCode) { const { code: genCode } = generate__default["default"](fnCode); const code = `return (${genCode})()`; try { const fn = new Function(code); if (typeof fn() === 'object') { return JSON.stringify(fn()); } return fn(); } catch (e) { return; } } function getValueFromGenerate(node) { let code = 'return'; const { code: genCode } = generate__default["default"](node); code += genCode; const fn = new Function(code); try { return fn(); } catch (e) { console.error(e); } } function computesFromStore(node) { if (node === undefined) { return false; } let fromStore = false; if (bt__namespace.isObjectMethod(node) || bt__namespace.isArrowFunctionExpression(node)) { fromStore = computesFromStore(node.body); } else if (bt__namespace.isObjectProperty(node)) { fromStore = computesFromStore(node.value); } else if (bt__namespace.isBlockStatement(node)) { fromStore = computesFromStore(node.body[node.body.length - 1]); } else if (bt__namespace.isCallExpression(traverse.NodePath)) { fromStore = computesFromStore(node.callee); } else if (bt__namespace.isMemberExpression(node)) { if (bt__namespace.isThisExpression(node.object)) { fromStore = node.property.name.toLowerCase().includes('store'); } else { fromStore = computesFromStore(node.object); } } else if (bt__namespace.isReturnStatement(node) || node.type.includes('Expression')) { fromStore = computesFromStore(node.argument); } return fromStore; } function getLiteralValue(node) { let data = ''; if (bt__namespace.isStringLiteral(node) || bt__namespace.isBooleanLiteral(node) || bt__namespace.isNumericLiteral(node)) { data = node.value.toString(); } return data; } function normalizePath(filePath) { if (/index$/.test(filePath)) { filePath = filePath.slice(0, filePath.length - 6); } if (!fs__namespace.existsSync(filePath)) { const _filePath = filePath + '.ts'; filePath = fs__namespace.existsSync(_filePath) ? _filePath : (filePath + '.js'); } if (fs__namespace.statSync(filePath).isDirectory()) { let _filePath = filePath + '/index.ts'; if (!fs__namespace.existsSync(_filePath)) { _filePath = filePath + '/index.js'; } filePath = _filePath; } return filePath; } function processTsType(node, options, originSource) { if (!options.jsFilePath) return; const jsFilePath = options.jsFilePath; const typeInfo = findType(node, options, originSource); if (!typeInfo) return; const typeName = typeInfo.type; if (typeName.includes('|')) { const arr = typeName.split('|'); let res = []; res = arr.map(item => { const isOriginType = originTypeJudge(item); const typeRes = typeNameHandler(item, jsFilePath); return { isOriginType, originName: item, typeRes: typeRes }; }); return res; } else { const isOriginType = originTypeJudge(typeName); const finalType = typeNameHandler(typeName, jsFilePath); return [{ isOriginType, originName: typeName, typeRes: finalType }]; } } const typeNameMap = {}; function typeNameHandler(typeName, jsFilePath) { let res; const isOriginType = originTypeJudge(typeName); if (isOriginType) { res = typeName; } else { if (typeNameMap[typeName]) return typeNameMap[typeName]; const node = findNodeByTypeName(typeName, jsFilePath); res = TypedocNodeToString(node); typeNameMap[typeName] = res; } return res; } function originTypeJudge(typeName) { return ['number', 'string', 'boolean'].includes(typeName); } function findType(node, options, originSource) { if (!options.jsFilePath) return; let type = ''; let isArray = false; if (bt__namespace.isTSArrayType(node.typeAnnotation)) { const element = node.typeAnnotation.elementType; type = originSource.slice(element.start || 0, element.end || 0); isArray = true; } else if (bt__namespace.isTSInterfaceBody(node.typeAnnotation)) ; else if (bt__namespace.isTSTypeReference(node.typeAnnotation)) { if (bt__namespace.isIdentifier(node.typeAnnotation.typeName)) { type = node.typeAnnotation.typeName.name; } } else if (bt__namespace.isTSUnionType(node.typeAnnotation)) { type = originSource.slice(node.typeAnnotation.start || 0, node.typeAnnotation.end || 0); } return { type, isArray }; } const TypedocJsonIds = {}; function findNodeByTypeName(typeName, jsFilePath) { function getIdMap(arr) { if (!arr?.length) return; arr.forEach(item => { // Typedoc.Models.ReflectionKind if (item.kind !== 2) { TypedocJsonIds[item.id] = item; } if (item.children) { getIdMap(item.children); } }); } Object.keys(TypedocJsonIds).length || getIdMap(typedocProject?.children); const sets = []; for (const key in TypedocJsonIds) { if (TypedocJsonIds[key].name === typeName) sets.push(TypedocJsonIds[key]); } if (sets.length === 1) return sets[0]; if (sets.length > 1) { for (let i = 0; i < sets.length; i++) { const source = sets[i].sources; if (source) { const filePath = source[0].fileName.replace(/(\.\.\/)n/, ''); if (jsFilePath.includes(filePath)) return sets[i]; } } } return undefined; } const TypedocReflectionKind = Typedoc__default["default"].Models.ReflectionKind; const nodeVisitor = { [TypedocReflectionKind.TypeAlias](node) { return TypedocTypeNodeVisit(node.type); }, [TypedocReflectionKind.Interface](node) { if (!node.children) return ''; let res = ''; node.children.forEach(item => { res = `${res}<br>&nbsp;&nbsp;${nodeVisitor[item.kind](item)};`; }); res = `{${res}<br>}`; return res; }, [TypedocReflectionKind.TypeLiteral](node) { return nodeVisitor[TypedocReflectionKind.Interface](node); }, [TypedocReflectionKind.Property](node) { const key = node.flags.isOptional ? node.name + '?' : node.name; const value = TypedocTypeNodeVisit(node.type); return `${key}: ${value}`; } }; const typeNodeVisitor = { array(node) { const elementTypeText = TypedocTypeNodeVisit(node.elementType); return elementTypeText + '[]'; }, reference(node) { if (node.name === 'Partial') { if (!node.typeArguments) return ''; const res = TypedocTypeNodeVisit(node.typeArguments[0]); return `Partial<${res}>`; } return TypedocNodeToString(node.reflection); }, intrinsic(node) { return node.name; }, union(node) { let res = ''; node.types.forEach((type, index) => { res = res + TypedocTypeNodeVisit(type); if (index < node.types.length - 1) { res = res + '\\|'; } }); return res; }, intersection(node) { let res = ''; node.types.forEach((type, index) => { res = res + TypedocTypeNodeVisit(type); if (index < node.types.length - 1) { res = res + '&'; } }); return res; }, reflection(node) { return TypedocNodeToString(node.declaration); }, literal(node) { return node.value; } }; function TypedocNodeToString(node) { if (!node) return ''; if (node?.comment?.blockTags[0].tag === '@typedocFollow') { return node.comment.blockTags[0].content[0].text; } if (nodeVisitor[node.kind]) { return nodeVisitor[node.kind](node); } else { console.log('出错啦,找不到对应kind'); return ''; } } function TypedocTypeNodeVisit(typeNode) { if (!typeNode) return ''; if (typeNodeVisitor[typeNode?.type]) { return typeNodeVisitor[typeNode.type](typeNode); } else { console.log('出错啦,找不到对应type'); return ''; } } let typedocProject; function setTypedocProject(project) { if (typedocProject || !project) return; typedocProject = project; } async function createTypedocProject(config) { const app = await Typedoc__default["default"].Application.bootstrapWithPlugins({ entryPoints: config.entryPoints, name: 'tt', skipErrorChecking: true, entryPointStrategy: 'expand', /** * 只能添加 被间接引用但没直接导出的 * typedoc 把文件中的 export 的内容当作入口,未在入口中的内容,不会被查找 */ plugin: ['typedoc-plugin-missing-exports'], // eslint-disable-next-line // @ts-ignore internalModule: 'notExport', // typedoc-plugin-missing-exports 使用,设置目录名 // excludeExternals: true, // 配合 typedoc-plugin-missing-exports excludeNotDocumented: true, // 移除没有添加注释的 excludeNotDocumentedKinds: ['Variable', 'CallSignature'] }); app.options.addReader(new Typedoc__default["default"].TSConfigReader()); const project = await app.convert(); return project; } function processPropValue(propValueNode, result, source, options) { if (isAllowPropsType(propValueNode)) { result.type = getTypeByTypeNode(propValueNode); } else if (bt__namespace.isObjectExpression(propValueNode)) { if (!propValueNode.properties.length) return; const allPropNodes = propValueNode.properties; const typeNode = []; const otherNodes = []; allPropNodes.forEach((node) => { if (node.key.name === 'type') { typeNode.push(node); } else if (node.key.name === 'optionalTypes') ; else { otherNodes.push(node); } }); // Prioritize `type` before processing `default`. // Because the difference in `type` will affect the way `default` is handled. if (typeNode.length > 0) { result.type = getTypeByTypeNode(typeNode[0].value); // Get descriptions of the type const typeDesc = getComments(typeNode[0]).default; if (typeDesc.length > 0) { result.typeDesc = typeDesc; } } // Processing props's default value otherNodes.forEach(node => { if (bt__namespace.isSpreadElement(node)) { return; } const n = node.key.name; if (n === 'default' || n === 'value') { if (!hasFunctionTypeDef(result.type)) { if (bt__namespace.isObjectMethod(node)) { // Using functionExpression instead of ObjectMethod const params = node.params || []; let body = node.body; if (!bt__namespace.isBlockStatement(body)) { body = bt__namespace.blockStatement(body); } const r = bt__namespace.functionExpression(null, params, body, false, false); result.default = runFunction(r); } else if (bt__namespace.isFunction(node.value)) { result.default = runFunction(node.value); } else if (bt__namespace.isTSAsExpression(node.value)) { if (!options || !options.jsFilePath) return; const tsTypes = processTsType(node.value, options, source); // if (tsType) result.default = source.slice(node.value.typeAnnotation.start || 0, node.value.typeAnnotation.end || 0); result.tsInfo = []; const tsInfo = result.tsInfo; if (tsTypes && tsInfo) { tsTypes.forEach(item => { if (!item.isOriginType) { tsInfo.push({ name: item.originName, type: item.typeRes }); } }); } } else { let start = node.value.start || 0; let end = node.value.end || 0; // if node.value is stringliteral , e.g: "string literal" need to exclude quote if (bt__namespace.isStringLiteral(node.value)) { start++; end--; } // type sucks, fix it use any... result.default = source.slice(start, end) || undefined; } } else { if (bt__namespace.isObjectMethod(node)) { result.default = generate__default["default"](node).code; } else if (bt__namespace.isFunction(node.value)) { result.default = generate__default["default"](node.value).code; } } // Get descriptions of the default value let _temp; if (!Array.isArray(result.describe)) { _temp = result.describe?.defaultDesc; } const defaultDesc = _temp || getComments(node).default; if (defaultDesc?.length > 0) { result.defaultDesc = defaultDesc; } } else if (n === 'required') { if (bt__namespace.isObjectProperty(node) && bt__namespace.isBooleanLiteral(node.value)) { result.required = node.value.value; } } else if (n === 'validator') { if (bt__namespace.isObjectMethod(node)) { result.validator = generate__default["default"](node).code; } else { result.validator = generate__default["default"](node.value).code; } // Get descriptions of the validator const validatorDesc = getComments(node).default; if (validatorDesc.length > 0) { result.validatorDesc = validatorDesc; } } }); } } function normalizeProps(props) { return props.map(prop => ({ type: null, name: prop })); } function getPropDecorator(classPropertyNode) { const decorators = classPropertyNode.decorators; if (!decorators) return; return decorators.find(deco => // @Prop() (bt__namespace.isCallExpression(deco.expression) && bt__namespace.isIdentifier(deco.expression.callee) && deco.expression.callee.name === 'Prop') || // @Prop (bt__namespace.isIdentifier(deco.expression) && deco.expression.name === 'Prop')); } function getArgumentFromPropDecorator(deco) { return bt__namespace.isCallExpression(deco.expression) ? deco.expression.arguments[0] : null; } function getTypeByTypeNode(typeNode) { if (bt__namespace.isIdentifier(typeNode)) return typeNode.name; if (bt__namespace.isArrayExpression(typeNode)) { if (!typeNode.elements.length) return null; return typeNode.elements .filter(node => node && bt__namespace.isIdentifier(node)) .map(node => node.name); } return null; } // The `type` of a prop should be an array of constructors or constructors // eg. String or [String, Number] function isAllowPropsType(typeNode) { return bt__namespace.isIdentifier(typeNode) || bt__namespace.isArrayExpression(typeNode); } function hasFunctionTypeDef(type) { if (typeof type === 'string') { return type.toLowerCase() === 'function'; } else if (Array.isArray(type)) { return type.map(a => a.toLowerCase()).some(b => b === 'function'); } return false; } function processDataValue(dataNode, result) { result.type = getTypeByDataNode(dataNode); result.default = getValueByDataNode(dataNode.value); } function getTypeByDataNode(node) { if (bt__namespace.isObjectMethod(node) || bt__namespace.isArrowFunctionExpression(node.value)) return 'Function'; const dataNode = node.value; if (bt__namespace.isIdentifier(dataNode)) return dataNode.name; if (bt__namespace.isAssignmentExpression(dataNode) || bt__namespace.isAssignmentPattern(dataNode)) { if (bt__namespace.isIdentifier(dataNode.left)) { return dataNode.left.name; } } if (bt__namespace.isLiteral(dataNode) || (bt__namespace.isExpression(dataNode) && !bt__namespace.isBinaryExpression(dataNode))) { return literalToType(dataNode.type); } return ''; } function getValueByDataNode(dataNode) { if (bt__namespace.isArrayExpression(dataNode)) { if (!dataNode.elements.length) return ''; return ('[' + dataNode.elements .filter(node => node && bt__namespace.isLiteral(node)) .map(node => getLiteralValue(node)) .toString() + ']'); } if (bt__namespace.isLiteral(dataNode)) { return getLiteralValue(dataNode); } if (bt__namespace.isAssignmentExpression(dataNode) || bt__namespace.isAssignmentPattern(dataNode)) { if (bt__namespace.isLiteral(dataNode.right)) { return getLiteralValue(dataNode.right); } } return ''; } function literalToType(literal) { const type = literal .replace('Literal', '') .replace('Expression', '') .replace('Numeric', 'Number'); return type; } /** * * @param eventName {string} The event name * @param cnode {bt.Node} Node with comments * @param result {EventResult} */ function processEventName(eventName, cnodePath, result) { const cnode = cnodePath.node; const syncRE = /^update:(.+)/; const eventNameMatchs = eventName.match(syncRE); // Mark as .sync if (eventNameMatchs) { result.isSync = true; result.syncProp = eventNameMatchs[1]; } let allComments = getComments(cnode); const prevPathKey = Number(cnodePath.key) - 1; if (allComments.nameGroup) { const nameGroup = allComments.nameGroup; const descGroup = allComments.descGroup || allComments.default; const argGroup = allComments.argGroup || allComments.arg; const versionGroup = allComments.versionGroup || allComments.version; result.arr = nameGroupHandler(nameGroup, descGroup, argGroup, versionGroup); } else if (!allComments.default.length && prevPathKey >= 0) { // Use the trailing comments of the prev node allComments = getComments(cnodePath.getSibling(prevPathKey).node, true); result.describe = allComments.default; result.argumentsDesc = allComments.arg; result.version = allComments.version; } else { result.describe = allComments.default; result.argumentsDesc = allComments.arg; result.version = allComments.version; } } function nameGroupHandler(nameGroup, descGroup, argGroup, versionGroup) { const reg = /^\[(.*?)\](.*)/; const names = nameGroup[0].match(reg)[1].replaceAll(' ', '').split(','); const map = {}; names.forEach(name => { map[name] = { isSync: false, syncProp: '', name, describe: [], argumentsDesc: [], version: [] }; descGroup?.forEach(desc => { const matchRes = desc.match(reg); if (!matchRes) { map[name].describe.push(desc); } else if (matchRes[1] && matchRes[2]) { if (matchRes[1].replaceAll(' ', '').split(',').filter(Boolean).includes(name)) { map[name].describe.push(matchRes[2].trim()); } } }); argGroup?.forEach(desc => { const matchRes = desc.match(reg); if (!matchRes) { map[name].argumentsDesc.push(desc); } else if (matchRes[1] && matchRes[2]) { if (matchRes[1].replaceAll(' ', '').split(',').filter(Boolean).includes(name)) { map[name].argumentsDesc.push(matchRes[2].trim()); } } }); versionGroup?.forEach(desc => { const matchRes = desc.match(reg); if (!matchRes) { map[name].version.push(desc); } else if (matchRes[1] && matchRes[2]) { if (matchRes[1].replaceAll(' ', '').split(',').filter(Boolean).includes(name)) { map[name].version.push(matchRes[2].trim()); } } }); }); return Object.keys(map).map(key => { return map[key]; }); } function getEmitDecorator(decorators) { if (!decorators || !decorators.length) return null; for (let i = 0; i < decorators.length; i++) { const exp = decorators[i].expression; if (bt__namespace.isCallExpression(exp) && bt__namespace.isIdentifier(exp.callee) && exp.callee.name === 'Emit') { return decorators[i]; } } return null; } /** * Used to identify ctx.children in the render function and use it as the default slot * @param functionPath The node path of the render function * @param onSlot */ function determineChildren(functionPath, onSlot) { if (!bt__namespace.isFunction(functionPath.node)) return; // Get the last argument of the render function and use it as the render context const lastParamNode = functionPath.node.params[functionPath.node.params.length - 1]; if (!lastParamNode || !bt__namespace.isIdentifier(lastParamNode)) return; // Get the binding of the context within the scope of the render function let contextBinding; const bindingKeys = Object.keys(functionPath.scope.bindings); for (let i = 0; i < bindingKeys.length; i++) { if (bindingKeys[i] === lastParamNode.name) { contextBinding = functionPath.scope.bindings[lastParamNode.name]; } } if (!contextBinding) return; // Determine ctx.childer contextBinding.referencePaths.forEach(refPath => { if (bt__namespace.isIdentifier(refPath.node) && refPath.parentPath && bt__namespace.isMemberExpression(refPath.parentPath.node) && bt__namespace.isIdentifier(refPath.parentPath.node.property) && refPath.parentPath.node.property.name === 'children') { const slotRes = { name: '— (默认插槽)', describe: '', backerDesc: '', scoped: false, bindings: {}, target: 'script' }; const commentsRes = bt__namespace.isExpressionStatement(refPath.parentPath.parentPath) ? getComments(refPath.parentPath.parentPath.node) : getComments(refPath.parentPath.node); slotRes.describe = commentsRes.default.join(''); slotRes.backerDesc = commentsRes.content ? commentsRes.content.join('') : ''; if (onSlot) onSlot(slotRes); } }); } class Seen { seenSet = new Set(); seen(label) { const yes = this.seenSet.has(label); if (!yes) this.seenSet.add(label); return yes; } } function findImportDeclaration(filePath, name) { filePath = normalizePath(filePath); const content = fs__namespace.readFileSync(filePath, 'utf-8'); const ast = parser$1.parse(content, { sourceType: 'module', plugins: ['typescript', 'jsx'] }); return traverseAst$1(ast, name, filePath); } function traverseAst$1(ast, name, filePath) { const variableResult = {}; let exportedResult; traverse__default["default"](ast, { // let a = xxx VariableDeclaration(rootPath) { rootPath.node.declarations.forEach(declaration => { const { id, init } = declaration; variableResult[id.name] = init; }); }, // a = xxx AssignmentExpression(rootPath) { const node = rootPath.node; variableResult[node.left.name] = node.right; }, // export a from 'xxx' ExportNamedDeclaration(rootPath) { const node = rootPath.node; if (node.source) { const specifiers = node.specifiers; for (let i = 0; i < specifiers.length; i++) { if (specifiers[i].exported.name === name) { exportedResult = { type: 'exportFrom', originName: specifiers[i].local.name, from: node.source.value }; break; } } } else if (node.declaration) { const declarations = node.declaration.declarations; for (let i = 0; i < declarations.length; i++) { if (declarations[i].id.name === name) { exportedResult = { type: 'exportVariable' }; break; } } } } }); if (exportedResult && exportedResult.type === 'exportFrom') { filePath = path.resolve(filePath.slice(0, filePath.lastIndexOf('/')), exportedResult.from); filePath = normalizePath(filePath); if (exportedResult.originName === 'default') { return findDefaultImportDeclaration(filePath); } } else if (exportedResult && exportedResult.type === 'exportVariable') { return variableResult[name]; } return { ast, filePath }; } function findDefaultImportDeclaration(filePath) { filePath = normalizePath(filePath); const content = fs__namespace.readFileSync(filePath, 'utf-8'); const ast = parser$1.parse(content, { sourceType: 'module', plugins: ['typescript', 'jsx'] }); return { ast, filePath }; } // 触发发布 function mergeMixinsOptions(parserRes) { // 只需要处理 props、methods、events // 基于 vue mergeOptions。若组件与 mixins 键名冲突,取组件内键值对,舍弃 mixins 内容 const { props = [], methods = [], events = [] } = parserRes; const removeOptions = [props, methods, events]; removeOptions.forEach(option => { const optionLevelArr = []; const saveObj = {}; const removeIndex = []; option.forEach((item, index) => { const level = item.level; if (!optionLevelArr[level]) optionLevelArr[level] = {}; const obj = optionLevelArr[level]; if (obj[item.name] || obj[item.name] === 0) { removeIndex.push(obj[item.name]); } else { obj[item.name] = index; } }); optionLevelArr.forEach(item => { Object.keys(item).forEach(key => { if (saveObj[key]) { removeIndex.push(item[key]); } else { saveObj[key] = item[key]; } }); }); removeIndex.sort((a, b) => a - b); removeIndex.forEach((item, index) => { option.splice(item - index, 1); }); }); } const MPX_CREATE_COMPONENT = 'createComponent'; const MPX_CREATE_PAGE = 'createPage'; const MPX_GET_MIXIN = 'getMixin'; let level = 0; function setOptionsLevel(num) { level = num; } function parseJavascript(ast, seenEvent, options, source = '') { // backward compatibility const seenSlot = new Seen(); const eventNameMap = {}; // let exportDefaultReferencePath: unknown = null const componentLevel = 0; const importDeclarationMap = {}; const importOriginNameMap = {}; const vueComponentVisitor = { Decorator(path) { if (bt__namespace.isCallExpression(path.node.expression) && bt__namespace.isIdentifier(path.node.expression.callee, { name: 'Component' }) && path.node.expression.arguments.length && bt__namespace.isObjectExpression(path.node.expression.arguments[0])) { path.traverse(vueComponentVisitor); } }, ObjectProperty(path$1) { const { onProp, onTsType, onMethod, onComputed, onName, onSlot, onMixIn, onData, onWatch, onExternalClasses } = options; // Processing name if (isVueOption(path$1, 'name', componentLevel)) { const componentName = path$1.node.value.value; if (onName) onName(componentName); } // Processing externalClasses for miniapp if (onExternalClasses && isVueOption(path$1, 'externalClasses', componentLevel)) { const valuePath = path$1.get('value'); if (bt__namespace.isArrayExpression(valuePath.node)) { const elementsPath = path$1.get('value.elements'); elementsPath.forEach(elePath => { const commentsRes = getComments(elePath.node); if (onExternalClasses) { const externalClassesResult = { name: elePath.node.value, describe: commentsRes.default }; onExternalClasses(externalClassesResult); } }); } } // Processing props if (onProp && isVueOption(path$1, 'props|properties', componentLevel)) { const valuePath = path$1.get('value'); if (bt__namespace.isArrayExpression(valuePath.node)) { // An array of strings const propsValue = getValueFromGenerate(valuePath.node); const propsRes = normalizeProps(propsValue); propsRes.forEach(prop => { if (onProp) onProp(prop); }); } else if (bt__namespace.isObjectExpression(valuePath.node)) { // An object valuePath.traverse({ ObjectProperty(propPath) { // Guarantee that this is the prop definition if (propPath.parentPath === valuePath) { const name = bt__namespace.isIdentifier(propPath.node.key) ? propPath.node.key.name : propPath.node.key.value; const propValueNode = propPath.node.value; const describe = getComments(propPath.node); const result = { name, type: null, describe: describe, version: describe.version, level }; processPropValue(propValueNode, result, source, options); if (result.tsInfo && onTsType) { onTsType(result.tsInfo); } onProp(result); } } }); } } // Processing mixins if (onMixIn && isVueOption(path$1, 'mixins', componentLevel)) { const properties = path$1.node.value.elements; properties.forEach((mixIn) => { let mixInpath = importDeclarationMap[mixIn.name]; if (mixInpath === importDeclarationMap[mixIn.name] && mixInpath[0] === '.') { mixInpath = path.resolve(options.basedir, mixInpath); } const { ast, filePath } = findImportDeclaration(mixInpath, mixIn.name); if (!filePath || !ast) return; const _source = fs__namespace.readFileSync(filePath, 'utf-8'); const _options = { ...options }; _options.basedir = path.resolve(filePath, '../'); setOptionsLevel(level + 1); parseJavascript(ast, seenEvent, _options, _source); setOptionsLevel(level - 1); }); } // Processing computed if (onComputed && isVueOption(path$1, 'computed', componentLevel) && bt__namespace.isObjectExpression(path$1.node.value)) { const properties = path$1.node .value.properties.filter(n => bt__namespace.isObjectMethod(n) || bt__namespace.isObjectProperty(n)); properties.forEach(node => { const commentsRes = getComments(node); const isFromStore = computesFromStore(node); // Collect only computed that have @vuese annotations if (commentsRes.vuese) { const result = { name: node.key.name, type: commentsRes.type, describe: commentsRes.default, isFromStore: isFromStore }; onComputed(result); } }); } // TODO: data 的注解 // if (onData && isVueOption(path, 'data', componentLevel)) { // if (options.isMpx) { // } else if () // } if (onData && isVueOption(path$1, 'data', componentLevel) && (bt__namespace.isObjectExpression(path$1.node.value) || bt__namespace.isArrowFunctionExpression(path$1.node.value))) { let value = bt__namespace.isArrowFunctionExpression(path$1.node.value) ? path$1.node.value.body : path$1.node.value; /** * data: () => { * return {} * } * if data property is something like above, should process its return statement * argument */ if (bt__namespace.isBlockStatement(value)) { const returnStatement = value.body.filter(n => bt__namespace.isReturnStatement(n))[0]; if (returnStatement && returnStatement.argument && bt__namespace.isObjectExpression(returnStatement.argument)) { value = returnStatement.argument; } } if (bt__namespace.isObjectExpression(value)) { const properties = value.properties.filter(n => bt__namespace.isObjectProperty(n)); properties.forEach(node => { if (bt__namespace.isSpreadElement(node)) { return; } const commentsRes = getComments(node); // Collect only data that have @vuese annotations if (commentsRes.vuese && bt__namespace.isObjectProperty(node)) { const result = { name: node.key.name, type: '', describe: commentsRes.default, default: '' }; processDataValue(node, result); onData(result); } }); } } // Processing methods if (onMethod && isVueOption(path$1, 'methods', componentLevel)) { const properties = path$1.node .value.properties.filter(n => bt__namespace.isObjectMethod(n) || bt__namespace.isObjectProperty(n)); properties.forEach(node => { const commentsRes = getComments(node); // Collect only methods that have @vuese annotations if (commentsRes.vuese) { const result = { name: node.key.name, describe: commentsRes.default, argumentsDesc: commentsRes.arg, returnDesc: commentsRes.return, version: commentsRes.version, level }; onMethod(result); } }); } // Processing watch if (onWatch && isVueOption(path$1, 'watch', componentLevel) && bt__namespace.isObjectExpression(path$1.node.value)) { const properties = path$1.node .value.properties.filter(n => bt__namespace.isObjectMethod(n) || bt__namespace.isObjectProperty(n)); properties.forEach(node => { const commentsRes = getComments(node); // Collect only data that have @vuese annotations if (commentsRes.vuese) { const result = { name: node.key.name, describe: commentsRes.default, argumentsDesc: commentsRes.arg }; onWatch(result); } }); } // functional component - `ctx.children` in the render function if (onSlot && isVueOption(path$1, 'render', componentLevel) && !seenSlot.seen('default')) { const functionPath = path$1.get('value'); determineChildren(functionPath, onSlot); } }, ObjectMethod(path) { const { onData } = options; // @Component: functional component - `ctx.children` in the render function if (options.onSlot && isVueOption(path, 'render', componentLevel) && !seenSlot.seen('default')) { determineChildren(path, options.onSlot); } // Data can be represented as a component or a method if (onData && isVueOption(path, 'data', componentLevel)) { path.node.body.body.forEach(body => { if (bt__namespace.isReturnStatement(body)) { const properties = body.argument.properties.filter(n => bt__namespace.isObjectMethod(n) || bt__namespace.isObjectProperty(n));