zelda-ast
Version:
zelda static analysis based on javascript ast.
240 lines (198 loc) • 7.92 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.UNRESOLVED_IDENTIFIER = undefined;
var _jscodeshift = require('jscodeshift');
var _jscodeshift2 = _interopRequireDefault(_jscodeshift);
var _Collection = require('jscodeshift/src/Collection');
var _Collection2 = _interopRequireDefault(_Collection);
var _lodash = require('lodash.once');
var _lodash2 = _interopRequireDefault(_lodash);
var _lodash3 = require('lodash.flatten');
var _lodash4 = _interopRequireDefault(_lodash3);
var _assert = require('assert');
var _assert2 = _interopRequireDefault(_assert);
var _index = require('../utils/index');
var utils = _interopRequireWildcard(_index);
var _Helper = require('./Helper');
var _Helper2 = _interopRequireDefault(_Helper);
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
_Helper2.default.register();
const UNRESOLVED_IDENTIFIER = exports.UNRESOLVED_IDENTIFIER = '__UNRESOLVED_IDENTIFIER__';
const { NodePath } = _jscodeshift2.default.types;
const methods = {
findRouteComponents() {
if (!this.hasReactModule()) return _Collection2.default.fromPaths([], this);
const pathes = [];
// 支持 ES6 Class
this.find(_jscodeshift2.default.ClassDeclaration).forEach(path => {
const superClass = path.node.superClass;
if (superClass) {
if (
// TODO: 处理 Component 和 React.Component 的来源信赖问题
// class A extends Component {}
_jscodeshift2.default.Identifier.check(superClass) && superClass.name === 'Component' ||
// class A extends React.Component {}
_jscodeshift2.default.MemberExpression.check(superClass) && isReactComponent(superClass)) {
pathes.push(path);
}
}
});
this.find(_jscodeshift2.default.FunctionDeclaration).forEach(path => {
if (hasJSXElement(path)) pathes.push(path);
});
// 支持 pure function
this.find(_jscodeshift2.default.VariableDeclarator, {
init: {
type: 'ArrowFunctionExpression'
}
}).forEach(path => {
if (hasJSXElement(path)) pathes.push(path);
});
function isReactComponent(node) {
return node.property.name === 'Component' && node.object.name === 'React';
}
function hasJSXElement(path) {
return (0, _jscodeshift2.default)(path).find(_jscodeshift2.default.JSXElement).size() > 0;
}
return _Collection2.default.fromPaths(pathes, this);
},
findDispatchCalls() {
function filterDispatch(path) {
// TODO: 识别 dispatch 和 put 的 alias
return path.name === 'dispatch' || path.name === 'put';
}
return this.find(_jscodeshift2.default.Identifier, filterDispatch).closest(_jscodeshift2.default.CallExpression);
},
findConnects() {
// TODO: 识别 connect alias
return this.find(_jscodeshift2.default.CallExpression, {
callee: {
type: 'Identifier',
name: 'connect'
}
});
},
findMapFunction() {
return this.map(path => {
const mapFnNode = path.value.arguments[0];
if (!mapFnNode) return null;
switch (mapFnNode.type) {
case 'ArrowFunctionExpression':
case 'FunctionExpression':
return new NodePath(mapFnNode);
case 'Identifier':
const scope = path.scope.lookup(mapFnNode.name);
if (scope) {
const newPath = scope.getBindings()[mapFnNode.name][0];
const p = newPath.parent;
const pNode = p.value;
if (pNode.type === 'VariableDeclarator') {
if (pNode.init.type === 'FunctionExpression' || pNode.init.type === 'ArrowFunctionExpression') {
return new NodePath(pNode.init);
}
}
if (pNode.type === 'FunctionDeclaration') {
return p;
}
}
throw new Error(`findMapFunction: unresolved`);
default:
throw new Error(`findMapFunction: unsupported path type ${mapFnNode.type}`);
}
});
},
getRouteComponentInfo(root) {
return this.simpleMap(path => {
return {
name: (0, _jscodeshift2.default)(path).getFirstComponentName(),
source: root.toSource(),
stateMappings: (() => {
const mapFunctions = root.findConnects().findMapFunction();
if (mapFunctions) {
return mapFunctions.getModulesFromMapFunction();
}
return [];
})(),
dispatches: (0, _jscodeshift2.default)(path).findDispatchCalls().getActionTypeFromCall()
};
});
},
getFirstComponentName() {
const node = this.get().value;
switch (node.type) {
case 'VariableDeclarator':
case 'ClassDeclaration':
case 'FunctionDeclaration':
return node.id.name;
case 'FunctionExpression':
(0, _assert2.default)(node.id && node.id.name, 'getFirstComponentName: component should not be anonymous');
return node.id.name;
default:
throw new Error('getFirstComponentName: unsupported node.type');
}
},
getModulesFromMapFunction() {
const result = this.simpleMap(path => {
const node = path.value;
const params = node.params;
if (!params || params.length === 0) {
return [];
}
switch (params[0].type) {
case 'Identifier':
return (0, _jscodeshift2.default)(node.body).find(_jscodeshift2.default.MemberExpression, {
object: {
type: 'Identifier',
name: params[0].name
}
}).simpleMap(path => {
return utils.getMemberProperty(path.value);
});
case 'ObjectPattern':
return params[0].properties.map(prop => prop.key.name);
default:
throw new Error(`getModulesFromMapFunction: unsupported param type ${params[0].type}`);
}
});
return (0, _lodash4.default)(result);
},
getActionTypeFromCall() {
const ret = this.simpleMap(path => {
const node = path.node;
(0, _assert2.default)(node.type === 'CallExpression', `getActionTypeFromCall: should be CallExpression`);
(0, _assert2.default)(node.arguments.length === 1, `getActionType: dispatch should be called with 1 argument, but got ${node.arguments.length}`);
const obj = node.arguments[0];
// TODO: Support dispatch(routerRedux.push({''}));
if (_jscodeshift2.default.CallExpression.check(obj)) {
console.warn(`[WARN] getActionTypeFromCall: don't support dispatch with CallExpression yet`);
return null;
}
(0, _assert2.default)(obj.type === 'ObjectExpression', `getActionType: dispatch should be called with Object, but got ${node.type}`);
const value = utils.getObjectProperty(obj, 'type');
if (value.type === 'Literal') {
return value.value;
} else if (value.type === 'Identifier') {
const result = (0, _jscodeshift2.default)(path).getVariableDeclarators(_ => value.name);
if (result.size()) {
return result.get().value.init.value;
} else {
return UNRESOLVED_IDENTIFIER;
}
} else if (value.type === 'TemplateLiteral') {
console.warn(`[WARN] getActionTypeFromCall: unsupported action type ${value.type}`);
} else {
throw new Error(`getActionTypeFromCall: unsupported action type ${value.type}`);
}
});
return ret.filter(item => !!item);
}
};
function register(jscodeshift = _jscodeshift2.default) {
jscodeshift.registerMethods(methods);
}
exports.default = {
register: (0, _lodash2.default)(register)
};