@mpxjs/vuese-parser
Version:
Vue file parser for automatic document generation
1,329 lines (1,311 loc) • 73.6 kB
JavaScript
'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> ${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));