const anyMatch = require('anymatch');const _ = require('lodash');const store = require('./store');const generator = require('@babel/generator').default;const t = require('@babel/types');function getNodeValue(node, path) { if (!node) { return null; } if (t.isStringLiteral(node)) { return _.get(node, 'value'); } if (t.isTemplateLiteral(node)) { const quasiList = _.get(node, 'quasis') || []; const expressions = _.get(node, 'expressions') || []; if (quasiList.length === 1 && expressions.length === 0) { return _.get(quasiList[0], 'value.raw'); } } if (t.isIdentifier(node)) { const varName = _.get(node, 'name'); const scope = path.scope.getBinding(varName); const varInitNode = _.get(scope, 'path.node.init'); return getNodeValue(varInitNode, path); } return generator(node, { minified: true }).code;}function checkVisitor(file) { return file.get('needVisitor');}/** * 将 method 值转大写,若无值,则赋默认值GET * @param {string} method * @returns */function upperCaseMethod(method) { return method ? method.toUpperCase() : 'GET';}function save(state, values) { const { file } = state; let all = file.get('requests'); all = _.uniqWith([...all, ...values], _.isEqual); file.set('requests', all);}/** * 获取参数对象中的特定属性 * @param {*} callPath callPath * @param {*} index 参数下标 * @param {*} prop 属性名 * @returns 属性值 */function getObjParamPropValue(callPath, index, prop) { const callNode = callPath.node; const argType = _.get(callNode, `arguments.${index}.type`); if (argType !== 'ObjectExpression') return undefined; const propNodes = _.get(callNode, `arguments.${index}.properties`); const find = propNodes.find((pn) => _.get(pn, 'key.name') === prop); const valueNode = _.get(find, 'value'); // 若是变量取初始值 if (valueNode && valueNode.type === 'Identifier') { const { name } = valueNode; const scope = callPath.scope.getBinding(name); const initNode = _.get(scope, 'path.node.init'); const code = getNodeValue(initNode); return code; } const code = getNodeValue(valueNode); return code || '';}/** * 从callPath的node中,获取 url 参数 * @param {object} callPath * @returns */function getUrlFromCallPath(callPath) { let apiString = ''; const callNode = callPath.node; const arg0Type = _.get(callNode, 'arguments.0.type'); // 第一个参数为模板字符串,普通字符串,拼接变量字符串 if (['TemplateLiteral', 'StringLiteral', 'BinaryExpression'].includes(arg0Type)) { const code = getNodeValue(_.get(callNode, 'arguments.0')); apiString = code; } // 第一个参数为变量取初始值 if (arg0Type === 'Identifier') { const name = _.get(callNode, 'arguments.0.name'); const scope = callPath.scope.getBinding(name); const apiNode = _.get(scope, 'path.node.init'); const code = getNodeValue(apiNode); apiString = code; } // 第一个参数为对象 if (arg0Type === 'ObjectExpression') { const code = getObjParamPropValue(callPath, 0, 'url'); apiString = code; } return apiString;}function getInfoFromCallPath(callPath) { const getInfoMethodMap = { // eslint-disable-next-line no-use-before-define MemberExpression: getMemberTypeInfo, // eslint-disable-next-line no-use-before-define CallExpression: getConfigTypeInfo, }; const nodeType = _.get(callPath, 'node.type'); const getFn = getInfoMethodMap[nodeType]; return getFn(callPath);}/** * 从MemberExpression类型的调用中获取api信息 * @param {object} memberPath * @returns */function getMemberTypeInfo(memberPath) { const { node } = memberPath; const method = _.get(node, 'property.name'); const { parentPath } = memberPath; const url = getUrlFromCallPath(parentPath); if (method === 'create') { const varPath = memberPath.findParent((p) => p.type === 'VariableDeclarator'); const name = _.get(varPath, 'node.id.name'); if (!name) return []; const bindings = varPath.scope.getBinding(name); return bindings.referencePaths.reduce((acc, cur) => { const callPath = cur.parentPath; const infos = getInfoFromCallPath(callPath); return acc.concat(infos); }, []); } if (method === 'request') { const callPath = memberPath.parentPath; return getInfoFromCallPath(callPath); } return [ { method: upperCaseMethod(method), url, }, ];}/** * 通过导入的库获取api信息 * @param {*} path import path * @returns */function getInfos(path) { const source = _.get(path, 'node.source.value'); const specifiers = _.get(path, 'node.specifiers') || []; if (source !== 'axios') return []; const importNames = specifiers .map((item) => _.get(item, 'local.name')); return importNames.reduce((iAcc, name) => { const bindings = path.scope.getBinding(name); return bindings.referencePaths.reduce((rAcc, bPath) => { const callPath = bPath.parentPath; const infos = getInfoFromCallPath(callPath); return rAcc.concat(infos); }, iAcc); }, []);}/** * 获取config配置方式的api信息 * @param {*} path * @returns */function getConfigTypeInfo(path) { const { node } = path; let method = ''; let apiString = ''; apiString = getUrlFromCallPath(path); if (!apiString) return []; const arg0Type = _.get(node, 'arguments.0.type'); // 第一个参数为对象 if (arg0Type === 'ObjectExpression') { method = getObjParamPropValue(path, 0, 'method'); } // 第二个参数为对象 const arg1Type = _.get(node, 'arguments.1.type'); if (arg1Type === 'ObjectExpression') { method = getObjParamPropValue(path, 1, 'method'); } return [{ method: upperCaseMethod(method), url: apiString, }];}const cache = [];module.exports = () => ({ pre(file) { const options = this.opts || {}; const { include, exclude, apiFileMatch, } = options; if (cache.includes(this.filename)) { file.set('needVisitor', false); return; } if (include) { const isIncludeCurrent = anyMatch(include, this.filename); if (!isIncludeCurrent) { file.set('needVisitor', false); return; } } if (exclude) { const isExcludeCurrent = anyMatch(exclude, this.filename); if (isExcludeCurrent) { file.set('needVisitor', false); return; } } if (apiFileMatch) { const isApiFile = anyMatch(apiFileMatch, this.filename); if (isApiFile) { file.set('needVisitor', false); return; } } file.set('needVisitor', true); cache.push(this.filename); file.set('requests', []); }, visitor: { ImportDeclaration(path, state) { const isContinue = checkVisitor(state.file); if (!isContinue) return; const infos = getInfos(path); infos.length && save(state, infos); }, }, post(file) { const isContinue = checkVisitor(file); if (!isContinue) return; const requests = file.get('requests'); requests.length && store.set(this.filename, { requestList: requests, }); },});