1. const anyMatch = require('anymatch');
    2. const _ = require('lodash');
    3. const store = require('./store');
    4. const generator = require('@babel/generator').default;
    5. const t = require('@babel/types');
    6. function getNodeValue(node, path) {
    7. if (!node) {
    8. return null;
    9. }
    10. if (t.isStringLiteral(node)) {
    11. return _.get(node, 'value');
    12. }
    13. if (t.isTemplateLiteral(node)) {
    14. const quasiList = _.get(node, 'quasis') || [];
    15. const expressions = _.get(node, 'expressions') || [];
    16. if (quasiList.length === 1 && expressions.length === 0) {
    17. return _.get(quasiList[0], 'value.raw');
    18. }
    19. }
    20. if (t.isIdentifier(node)) {
    21. const varName = _.get(node, 'name');
    22. const scope = path.scope.getBinding(varName);
    23. const varInitNode = _.get(scope, 'path.node.init');
    24. return getNodeValue(varInitNode, path);
    25. }
    26. return generator(node, { minified: true }).code;
    27. }
    28. function checkVisitor(file) {
    29. return file.get('needVisitor');
    30. }
    31. /**
    32. * 将 method 值转大写,若无值,则赋默认值GET
    33. * @param {string} method
    34. * @returns
    35. */
    36. function upperCaseMethod(method) {
    37. return method ? method.toUpperCase() : 'GET';
    38. }
    39. function save(state, values) {
    40. const { file } = state;
    41. let all = file.get('requests');
    42. all = _.uniqWith([...all, ...values], _.isEqual);
    43. file.set('requests', all);
    44. }
    45. /**
    46. * 获取参数对象中的特定属性
    47. * @param {*} callPath callPath
    48. * @param {*} index 参数下标
    49. * @param {*} prop 属性名
    50. * @returns 属性值
    51. */
    52. function getObjParamPropValue(callPath, index, prop) {
    53. const callNode = callPath.node;
    54. const argType = _.get(callNode, `arguments.${index}.type`);
    55. if (argType !== 'ObjectExpression') return undefined;
    56. const propNodes = _.get(callNode, `arguments.${index}.properties`);
    57. const find = propNodes.find((pn) => _.get(pn, 'key.name') === prop);
    58. const valueNode = _.get(find, 'value');
    59. // 若是变量取初始值
    60. if (valueNode && valueNode.type === 'Identifier') {
    61. const { name } = valueNode;
    62. const scope = callPath.scope.getBinding(name);
    63. const initNode = _.get(scope, 'path.node.init');
    64. const code = getNodeValue(initNode);
    65. return code;
    66. }
    67. const code = getNodeValue(valueNode);
    68. return code || '';
    69. }
    70. /**
    71. * 从callPath的node中,获取 url 参数
    72. * @param {object} callPath
    73. * @returns
    74. */
    75. function getUrlFromCallPath(callPath) {
    76. let apiString = '';
    77. const callNode = callPath.node;
    78. const arg0Type = _.get(callNode, 'arguments.0.type');
    79. // 第一个参数为模板字符串,普通字符串,拼接变量字符串
    80. if (['TemplateLiteral', 'StringLiteral', 'BinaryExpression'].includes(arg0Type)) {
    81. const code = getNodeValue(_.get(callNode, 'arguments.0'));
    82. apiString = code;
    83. }
    84. // 第一个参数为变量取初始值
    85. if (arg0Type === 'Identifier') {
    86. const name = _.get(callNode, 'arguments.0.name');
    87. const scope = callPath.scope.getBinding(name);
    88. const apiNode = _.get(scope, 'path.node.init');
    89. const code = getNodeValue(apiNode);
    90. apiString = code;
    91. }
    92. // 第一个参数为对象
    93. if (arg0Type === 'ObjectExpression') {
    94. const code = getObjParamPropValue(callPath, 0, 'url');
    95. apiString = code;
    96. }
    97. return apiString;
    98. }
    99. function getInfoFromCallPath(callPath) {
    100. const getInfoMethodMap = {
    101. // eslint-disable-next-line no-use-before-define
    102. MemberExpression: getMemberTypeInfo,
    103. // eslint-disable-next-line no-use-before-define
    104. CallExpression: getConfigTypeInfo,
    105. };
    106. const nodeType = _.get(callPath, 'node.type');
    107. const getFn = getInfoMethodMap[nodeType];
    108. return getFn(callPath);
    109. }
    110. /**
    111. * 从MemberExpression类型的调用中获取api信息
    112. * @param {object} memberPath
    113. * @returns
    114. */
    115. function getMemberTypeInfo(memberPath) {
    116. const { node } = memberPath;
    117. const method = _.get(node, 'property.name');
    118. const { parentPath } = memberPath;
    119. const url = getUrlFromCallPath(parentPath);
    120. if (method === 'create') {
    121. const varPath = memberPath.findParent((p) => p.type === 'VariableDeclarator');
    122. const name = _.get(varPath, 'node.id.name');
    123. if (!name) return [];
    124. const bindings = varPath.scope.getBinding(name);
    125. return bindings.referencePaths.reduce((acc, cur) => {
    126. const callPath = cur.parentPath;
    127. const infos = getInfoFromCallPath(callPath);
    128. return acc.concat(infos);
    129. }, []);
    130. }
    131. if (method === 'request') {
    132. const callPath = memberPath.parentPath;
    133. return getInfoFromCallPath(callPath);
    134. }
    135. return [
    136. {
    137. method: upperCaseMethod(method),
    138. url,
    139. },
    140. ];
    141. }
    142. /**
    143. * 通过导入的库获取api信息
    144. * @param {*} path import path
    145. * @returns
    146. */
    147. function getInfos(path) {
    148. const source = _.get(path, 'node.source.value');
    149. const specifiers = _.get(path, 'node.specifiers') || [];
    150. if (source !== 'axios') return [];
    151. const importNames = specifiers
    152. .map((item) => _.get(item, 'local.name'));
    153. return importNames.reduce((iAcc, name) => {
    154. const bindings = path.scope.getBinding(name);
    155. return bindings.referencePaths.reduce((rAcc, bPath) => {
    156. const callPath = bPath.parentPath;
    157. const infos = getInfoFromCallPath(callPath);
    158. return rAcc.concat(infos);
    159. }, iAcc);
    160. }, []);
    161. }
    162. /**
    163. * 获取config配置方式的api信息
    164. * @param {*} path
    165. * @returns
    166. */
    167. function getConfigTypeInfo(path) {
    168. const { node } = path;
    169. let method = ''; let apiString = '';
    170. apiString = getUrlFromCallPath(path);
    171. if (!apiString) return [];
    172. const arg0Type = _.get(node, 'arguments.0.type');
    173. // 第一个参数为对象
    174. if (arg0Type === 'ObjectExpression') {
    175. method = getObjParamPropValue(path, 0, 'method');
    176. }
    177. // 第二个参数为对象
    178. const arg1Type = _.get(node, 'arguments.1.type');
    179. if (arg1Type === 'ObjectExpression') {
    180. method = getObjParamPropValue(path, 1, 'method');
    181. }
    182. return [{
    183. method: upperCaseMethod(method),
    184. url: apiString,
    185. }];
    186. }
    187. const cache = [];
    188. module.exports = () => ({
    189. pre(file) {
    190. const options = this.opts || {};
    191. const {
    192. include,
    193. exclude,
    194. apiFileMatch,
    195. } = options;
    196. if (cache.includes(this.filename)) {
    197. file.set('needVisitor', false);
    198. return;
    199. }
    200. if (include) {
    201. const isIncludeCurrent = anyMatch(include, this.filename);
    202. if (!isIncludeCurrent) {
    203. file.set('needVisitor', false);
    204. return;
    205. }
    206. }
    207. if (exclude) {
    208. const isExcludeCurrent = anyMatch(exclude, this.filename);
    209. if (isExcludeCurrent) {
    210. file.set('needVisitor', false);
    211. return;
    212. }
    213. }
    214. if (apiFileMatch) {
    215. const isApiFile = anyMatch(apiFileMatch, this.filename);
    216. if (isApiFile) {
    217. file.set('needVisitor', false);
    218. return;
    219. }
    220. }
    221. file.set('needVisitor', true);
    222. cache.push(this.filename);
    223. file.set('requests', []);
    224. },
    225. visitor: {
    226. ImportDeclaration(path, state) {
    227. const isContinue = checkVisitor(state.file);
    228. if (!isContinue) return;
    229. const infos = getInfos(path);
    230. infos.length && save(state, infos);
    231. },
    232. },
    233. post(file) {
    234. const isContinue = checkVisitor(file);
    235. if (!isContinue) return;
    236. const requests = file.get('requests');
    237. requests.length && store.set(this.filename, {
    238. requestList: requests,
    239. });
    240. },
    241. });