UNPKG

magix-combine

Version:

合并Magix View的html,js,css成一个js文件,并检测html,js,css中可能存在的问题

646 lines (633 loc) 25.7 kB
/* 检测magix项目中数据接口的调用,根据异常监控平台的数据,绝大多数的异常都是数据访问异常引起的,比如response.data.list,而某些情况后端并未输出response.data,导致前端拿数据失败。因此尝试静态分析代码,找到可能出问题的数据访问,把问题解决在上线前 */ let stringReg = /^['"]/; let slog = require('./util-log'); let chalk = require('chalk'); /* let fn=(err,bag)=>{ var list=bag.get('list'); console.log('list',list.a);//list.a可能出错 if(list){ console.log(list.a);//ok } var list=bag.get('list',{}); console.log('list',list.a);//ok var b=list.c; console.log(b.x);//b.x可能出错 if(b){ console.log(b.x);//ok } if(b&&b.x){ console.log(b.x.y);//ok } if(b?b.c:b.d){ console.log(b.c.z);//可能出错 } }; */ let isWrapper = params => { //快速检测,跳过包装的define if (params[0].name == 'S') { //kissy return true; } if (params.length == 2) { if (params[0].name == 'exports' && params[1].name == 'module') { return true; } } if (params.length == 3) { if (params[0].name == 'require' && params[1].name == 'exports' && params[2].name == 'module') { return true; } } return false; }; let findLeftRNIndex = (tmpl, start) => { let max = start; while (--max) { let c = tmpl.charAt(max); if (c == '\r' || c == '\n') { return max; } } return max; }; let findRightRNIndex = (tmpl, start) => { let max = tmpl.length; while (start < max) { let c = tmpl.charAt(start); if (c == '\r' || c == '\n') { return start; } start++; } return start; }; //检测是否为bag.get('xx')调用 //首先只能是bag.get,另外bag要出现在参数中 // let isBagCall = (node, paramsObject) => { let prop = node.callee.property; let co = node.callee.object; let coName = co && co.name; //形如 bag.get('xxx',[]);的情况, if (prop && prop.name == 'get' && paramsObject[coName]) { return true; } return false; }; let isNotBagParams = (node, paramsObject) => { let prop = node.callee.property; let co = node.callee.object; let coName = co && co.name; if (!prop || !(prop.name == 'get' || prop.name == 'set') || !paramsObject[coName]) { return true; } return false; }; //检测是否缺少默认值 let isMissingDefaultValueBagCall = node => { let args = node.arguments; let a0IsString = false; if (args && args.length > 0) { a0IsString = stringReg.test(args[0].raw); } return args.length == 1 && a0IsString; }; //获取是否是bag.get调用,同时检测是否可能出错 let maybeErrorBagCall = (node, paramsObject, dname) => { let start = node; let count = dname ? 1 : 0; let key, power, isBC, missingDefault, dType; while (start) { //bag('xx').list.xx 这种情况不安全,需要提示 if (start.type == 'CallExpression') { if (isBagCall(start, paramsObject)) { isBC = true; power = count; if (isMissingDefaultValueBagCall(start) || count > 1) { key = start.start + '@' + start.end; missingDefault = true; } else if (start.arguments.length == 2) { let a1 = start.arguments[1]; dType = a1.type; } break; } } count++; start = start.object; } return { missingDefault, isBC, dType, power, //跟在方法调用后的几层属性,如bag.get('xx').aa.bb key }; }; //获取指向服务端返回的数据且表达式最长的一个 let getLongestExpr = (node, varTracker, sge, oexpr) => { if (node.type == 'Identifier') { if (varTracker[node.name]) { return 1; } } else if (node.type == 'MemberExpression') { let count = 1; let start = node; while (start.object) { start = start.object; count++; } if (varTracker[start.name]) { return count; } } else if (node.type == 'ConditionalExpression') { let a = getLongestExpr(node.alternate, varTracker); let c = getLongestExpr(node.consequent, varTracker); return Math.max(a, c); } else if (node.type == 'LogicalExpression') { if (sge) { if (node.operator == '&&' || node.left.value) { sge(node.left, oexpr); } } return getLongestExpr(node.right, varTracker); } return -1; }; module.exports = (node, comments, tmpl, e) => { let params = node.params; if (params.length <= 1 || isWrapper(params)) { //参数要大于1个,因为回调形式为(err,bag)=>{} return; } let paramsObject = Object.create(null); let usedParams = Object.create(null); let notBagParams = Object.create(null); let maybeError = []; let varTracker = Object.create(null); let safeguard = []; let safeguardMap = []; let memberExpressions = []; let addedME = Object.create(null); let callPoints = Object.create(null); //获取最近的保护父级 let getNearestParent = r => { let p; for (let i = 0, sg; i < safeguard.length; i++) { sg = safeguard[i]; if (sg.start <= r.start && r.end <= sg.end) { p = sg; } } return p; }; //获取所有父及祖先上的保护变量 let getParentSafed = r => { let safed = Object.create(null); for (let i = 0, sg; i < safeguard.length; i++) { sg = safeguard[i]; if (sg.start <= r.start && r.end <= sg.end) { Object.assign(safed, sg.safed); } } return safed; }; let uncheck = p => { do { if (comments[p]) { if (comments[p].text == 'mc-uncheck') { return true; } } let c = tmpl.charAt(p); if (c != ' ' && c != ';' && c != '\r' && c != '\n' && c != '\t' && c != '\f' && c != '\v' && c != '\u00A0' && c != '\uFEFF' && c != '\u2028' && c != '\u2029') { return false; } } while (p++ < tmpl.length); }; for (let i = 1, p; i < params.length; i++) { //记录有哪些形参,第一个是错误,不用记录 p = node.params[i]; paramsObject[p.name] = 1; //记录有哪些参数 } //处理赋值 let assign = (expr, oexpr, vname, sge, dname) => { if (expr.type == 'MemberExpression' || expr.type == 'CallExpression') { //var a=bag.get('xx')或var a=bag.get('xx').list的情况 let info = maybeErrorBagCall(expr, paramsObject, dname); if (info.isBC) { //检测是否包含bag.get varTracker[vname] = { type: 'bc', value: [vname], dt: info.dType, md: info.missingDefault, power: info.power }; } else if (expr.type != 'CallExpression') { //var a=b.c.d情况 let start = expr; let values = []; while (start.object) { values.push(tmpl.substring(start.property.start, start.property.end)); start = start.object; } if (start.name) { let p = varTracker[start.name]; //这是指向服务端返回数据对象的变量 if (p) { values.push(start.name); values = values.reverse(); if (p.type == 'ae') { //如果父级是变量引用,则父级的加上自身的 /* var a=bag.get('list'); var b=a.c; => b=['a','c'] var d=b.z; => d=['a','c','z'] 每个变量直接获取到服务端返回的对象的那个变量上,避免中间的变量引用查找 */ values = p.value.concat(values.slice(1)); } let np = { type: 'ae', value: values }; varTracker[vname] = np; //记录 } } } //var a=b的情况 } else if (expr.type == 'Identifier') { let p = varTracker[expr.name]; if (p) { /* var list=bag.get('list'); var a=list.xx; var b=a; => b=['list','xxx']; */ varTracker[vname] = { type: 'ae', value: p.value }; } //var b=bag&&bag.x&&bag.x.y情况 } else if (expr.type == 'LogicalExpression') { //左边是保护区 //if (expr.operator == '&&' || expr.left.value) { sge(expr.left, oexpr); //} assign(expr.right, oexpr, vname, sge); //let l = getLongestExpr(expr.left, varTracker); //let r = getLongestExpr(expr.right, varTracker); //if (l > -1 || r > -1) { // if (l > r) { // assign(expr.left, oexpr, vname, sge); // } else { // //把右边的赋值给变量 // assign(expr.right, oexpr, vname, sge); // } //} //var b=bag?bag.list:bag.default; } else if (expr.type == 'ConditionalExpression') { sge(expr.test, oexpr); //test是保护区 //获取哪个包含指向服务端的数据且是最长的 let c = getLongestExpr(expr.consequent, varTracker, sge, oexpr); let a = getLongestExpr(expr.alternate, varTracker, sge, oexpr); let aexpr; /* var a=list?list.d:[]; */ if (c > -1 || a > -1) { //包含指向服务端数据的表达式 if (c > a) { aexpr = expr.consequent; } else { aexpr = expr.alternate; } assign(aexpr, oexpr, vname, sge); } } else { let p = varTracker[vname]; if (p) { safeguard.push({ safed: { [vname]: 1 }, start: expr.start, end: tmpl.length }); } } }; //保护表达式 let safeguardExpression = (iexpr, expr) => { let key = expr.start + '@' + expr.end; //if(list) if (iexpr.type == 'Identifier') { let vname = iexpr.name; let p = varTracker[vname]; //保护的这个变量指向服务端数据 if (p) { let r = varTracker[p.value[0]]; let pInfo = safeguardMap[key]; //同样位置有多个 if (pInfo) { //只需要添加到原来中即可 pInfo.safed[p.value.join('\r')] = 1; } else { let pSafed = getParentSafed(expr); //获取父级的保护,把父级的merge进来 safeguard.push(safeguardMap[key] = { //记录保护 safed: Object.assign({ [p.value[0]]: !r.power && !r.md, [p.value.join('\r')]: 1 }, pSafed), start: expr.start, end: expr.end }); } } //if (list.a) } else if (iexpr.type == 'MemberExpression') { let start = iexpr; let keys = []; while (start.object) { keys.push(tmpl.substring(start.property.start, start.property.end)); start = start.object; } let p = varTracker[start.name]; if (p) { let pInfo = safeguardMap[key]; let r = varTracker[p.value[0]]; //console.log(key, pInfo, keys); let vname = p.value.concat(keys.reverse()); if (pInfo) { pInfo.safed[vname.join('\r')] = 1; } else { let pSafed = getParentSafed(expr); safeguard.push(safeguardMap[key] = { safed: Object.assign({ [p.value[0]]: !r.power && !r.md, [vname.join('\r')]: 1 }, pSafed), start: expr.start, end: expr.end }); } } //if(list&&list.a&&list.a.b||list.c) } else if (iexpr.type == 'LogicalExpression') { //debugger; //if (iexpr.operator == '&&') { safeguardExpression(iexpr.left, expr); safeguardExpression(iexpr.right, expr); //} else if (iexpr.operator == '||') { // if (iexpr.left.value) { // safeguardExpression(iexpr.left, expr); // safeguardExpression(iexpr.right, expr); // } else { //考虑左侧可以检测出如if(0||right) if(false||right)的情况,这种需要处理右边的表达式 // safeguardExpression(iexpr.right, expr); // } //} //if((a=list)) } else if (iexpr.type == 'AssignmentExpression') { let vname = iexpr.left.name; let init = iexpr.right; safeguardExpression(init, expr); assign(iexpr.right, expr, vname, safeguardExpression); //if(list?list.f:list.d) } else if (iexpr.type == 'ConditionalExpression') { safeguardExpression(iexpr.test, expr); /* if(list?list.d:list.f){ console.log(list.d.x);//这句可能会有问题 } */ //let c = getLongestExpr(iexpr.consequent, varTracker); //let a = getLongestExpr(iexpr.alternate, varTracker); //if (c == -1 || a == -1) { //如果这2个都包含指向服务端数据的表达式,则一个也不保护 // if (c != -1) { // safeguardExpression(iexpr.consequent, expr); // } else { // safeguardExpression(iexpr.alternate, expr); // } //} } else if (iexpr.type == 'UnaryExpression') { //debugger; safeguardExpression(iexpr.argument, expr); } }; let walk = expr => { if (Array.isArray(expr) || expr instanceof Object) { if (expr.type == 'CallExpression') { let co = expr.callee.object; let coName = co && co.name; let notBag = isNotBagParams(expr, paramsObject); if (notBag) { if (coName) { notBagParams[coName] = 1; } } else { callPoints[expr.start] = expr; let isBC = isBagCall(expr, paramsObject); if (isBC) { usedParams[coName] = 1; //形如 bag.get('xxx')的情况,这种要提示用户 if (isMissingDefaultValueBagCall(expr)) { let nearRN = findLeftRNIndex(tmpl, expr.start); let part = tmpl.substring(expr.start, expr.end); let near = (tmpl.substring(nearRN, expr.start) + part).trim(); let replacement = near.slice(0, -1) + ',defaultValue)'; maybeError.push({ type: 'bc', pos: expr.start, part, near, replacement }); } } } } else if (expr.type == 'MemberExpression') { if (!addedME[expr.start]) { addedME[expr.start] = 1; let ce = callPoints[expr.start]; let info = maybeErrorBagCall(ce || expr, paramsObject); if (info.key) { if (info.missingDefault && info.power) { let left = findLeftRNIndex(tmpl, expr.start); let right = findRightRNIndex(tmpl, expr.end); let near = tmpl.substring(left, right).trim(); memberExpressions.push({ type: 'bc', part: tmpl.substring(expr.start, expr.end), near: near, start: expr.start, end: expr.end }); } } else { let start = expr; let values = []; while (start.object) { values.push(tmpl.substring(start.property.start, start.property.end)); start = start.object; } if (start.name) { let p = varTracker[start.name]; if (p) { values.push(start.name); values = values.reverse(); if (p.type == 'ae') { values = p.value.concat(values.slice(1)); } /* 方法调用 var list=bag.get('list'); if(me.a&&list.hasOwnProperty('xx')){ } */ if (!p.power && !p.md) { if ((p.dt == 'ArrayExpression' && values.length <= 3) || values.length <= 2) { return; } } let left = findLeftRNIndex(tmpl, expr.start); let right = findRightRNIndex(tmpl, expr.end); let near = tmpl.substring(left, right).trim(); let uc = uncheck(expr.end); if (!uc) { memberExpressions.push({ values: values, start: expr.start, near: near, part: tmpl.substring(expr.start, expr.end), end: expr.end }); } } } } } } else if (expr.type == 'VariableDeclarator' || expr.type == 'AssignmentExpression') { if (expr.type == 'VariableDeclarator') { let init = expr.init; if (init) { let vname = expr.id.name; if (expr.id.type == 'ObjectPattern') { for (let dp of expr.id.properties) { let dname = dp.value.name; assign(init, expr, dname, safeguardExpression, dname); } } else if (expr.id.type == 'ArrayPattern') { for (let em of expr.id.elements) { let dname = em.name; assign(init, expr, dname, safeguardExpression, dname); } } else if (vname) { assign(init, expr, vname, safeguardExpression); } } } else if (expr.left.type == 'MemberExpression') { let start = expr.left; while (start.object) { start = start.object; } if (start.name && varTracker[start.name]) { let left = findLeftRNIndex(tmpl, expr.start); let right = findRightRNIndex(tmpl, expr.end); let near = tmpl.substring(left, right).trim(); maybeError.push({ type: 'ao', part: tmpl.substring(expr.start, expr.end), near, pos: expr.start }); } } else if (expr.left.type == 'Identifier') { let init = expr.right; let vname = expr.left.name; if (init) { assign(init, expr, vname, safeguardExpression); } } else if (expr.left.type == 'ObjectPattern') { let init = expr.right; for (let dp of expr.left.properties) { let dname = dp.value.name; assign(init, expr, dname, safeguardExpression, dname); } } else if (expr.left.type == 'ArrayPattern') { let init = expr.right; for (let em of expr.left.elements) { let dname = em.name; assign(init, expr, dname, safeguardExpression, dname); } } } else if (expr.type == 'IfStatement') { let iftest = expr.test; safeguardExpression(iftest, expr); } if (Array.isArray(expr)) { for (let i = 0; i < expr.length; i++) { walk(expr[i]); } } else if (expr instanceof Object) { for (let p in expr) { walk(expr[p]); } } } }; walk(node.body.body); for (let p in notBagParams) { delete usedParams[p]; } //定义的参数都用了,如果某一个没用到,则这个接口也无须请求 if (Object.keys(paramsObject).length == Object.keys(usedParams).length) { memberExpressions.forEach(it => { if (it.type == 'bc') { //bag call的调用 maybeError.push({ pos: it.start, near: it.near, part: it.part }); } else { let p = getNearestParent(it); //获取保护 if (p) { let i = 1; let values = it.values; while (i < values.length) { //当前成员路径需要都在保护里,如果不在则可能有问题 let key = values.slice(0, i).join('\r'); //let vt = varTracker[key]; if (!p.safed[key]) { maybeError.push({ pos: it.start, near: it.near, part: it.part }); break; } i++; } } else { //找不到,提示 maybeError.push({ pos: it.start, near: it.near, part: it.part }); } } }); maybeError.sort((a, b) => a.pos - b.pos).forEach(it => { if (it.type == 'bc') { slog.ever(chalk.red('avoid use: ' + it.part), 'at', chalk.grey(e.shortFrom), 'use', chalk.red(it.replacement), 'instead'); } else if (it.type == 'ao') { slog.ever(chalk.red('avoid use: ' + it.part), 'at', chalk.grey(e.shortFrom), 'more info:', chalk.magenta('https://github.com/thx/magix/issues/38')); } else { slog.ever(chalk.red('may trigger an error: ' + it.part), 'at', chalk.grey(e.shortFrom), 'near', chalk.magenta(it.near)); } }); //console.log(safeguardMap, '@@@', memberExpressions, '@@@', varTracker, '@@@', safeguard); } };