UNPKG

zelda-ast

Version:

zelda static analysis based on javascript ast.

298 lines (253 loc) 9.11 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _jscodeshift = require('jscodeshift'); var _jscodeshift2 = _interopRequireDefault(_jscodeshift); var _lodash = require('lodash.once'); var _lodash2 = _interopRequireDefault(_lodash); var _assert = require('assert'); var _assert2 = _interopRequireDefault(_assert); var _Helper = require('./Helper'); var _Helper2 = _interopRequireDefault(_Helper); var _index = require('../utils/index'); var utils = _interopRequireWildcard(_index); 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 _check = (node, name) => { return node.type === 'Identifier' && node.name === name || node.type === 'Literal' && node.value === name; }; const methods = { findModels(namespace) { return this.find(_jscodeshift2.default.ObjectExpression, node => { const props = node.properties.reduce((memo, prop) => { if (_jscodeshift2.default.Property.check(prop)) { if (prop.key.name === 'namespace') { if (!_jscodeshift2.default.Literal.check(prop.value)) { return {}; } memo[prop.key.name] = prop.value.value; } else { memo[prop.key.name] = true; } } return memo; }, {}); if (!props.namespace) return false; if (namespace && props.namespace !== namespace) return false; // state 不是必须的 // 但为增加准确性, 还需要声明除 namespace 以外的其他任一项 return props.state || props.reducers || props.effects || props.subscriptions; }); }, updateNamespace(newNamespace) { return this.forEach(path => { path.node.properties.forEach(prop => { if (_jscodeshift2.default.Property.check(prop) && prop.key.name === 'namespace') { prop.value = _jscodeshift2.default.literal(newNamespace); } }); }); }, updateState(source) { return this.forEach(path => { path.node.properties.forEach(prop => { if (_jscodeshift2.default.Property.check(prop) && prop.key.name === 'state') { prop.value = utils.getExpression(source); } }); }); }, addState(name, source) { this._addModelItem(name, source, { itemsKey: 'state', defaultSource: '{}' }); }, addReducer(name, source) { this._addModelItem(name, source, { itemsKey: 'reducers', defaultSource: 'function(state) {\n return state;\n}' }); }, addEffect(name, source) { this._addModelItem(name, source, { itemsKey: 'effects', defaultSource: '*function(action, { call, put, select }) {\n}' }); }, addSubscription(name, source) { this._addModelItem(name, source, { itemsKey: 'subscriptions', defaultSource: 'function({ dispatch, history }) {\n}' }); }, /** * @private */ _addModelItem(name, source, { itemsKey, defaultSource }) { return this.forEach(path => { let items = null; path.node.properties.forEach(prop => { if (_jscodeshift2.default.Property.check(prop) && prop.key.name === itemsKey) { (0, _assert2.default)(_jscodeshift2.default.ObjectExpression.check(prop.value), `_addModelItem: ${itemsKey} should be ObjectExpression, but got ${prop.value.type}`); items = prop; } }); if (!items) { items = _jscodeshift2.default.property('init', _jscodeshift2.default.identifier(itemsKey), _jscodeshift2.default.objectExpression([])); path.node.properties.push(items); } const item = _jscodeshift2.default.property('init', _jscodeshift2.default.identifier(name), utils.getExpression(source || defaultSource)); items.value.properties.push(item); }); }, updateReducer(name, source) { this._updateModelItem(name, source, { itemsKey: 'reducers' }); }, updateEffect(name, source) { this._updateModelItem(name, source, { itemsKey: 'effects' }); }, updateSubscription(name, source) { this._updateModelItem(name, source, { itemsKey: 'subscriptions' }); }, /** * @private */ _updateModelItem(name, source, { itemsKey }) { return this.forEach(path => { let items = null; path.node.properties.forEach(prop => { if (_jscodeshift2.default.Property.check(prop) && prop.key.name === itemsKey) { (0, _assert2.default)(_jscodeshift2.default.ObjectExpression.check(prop.value), `_updateModelItem: ${itemsKey} should be ObjectExpression, but got ${prop.value.type}`); items = prop; } }); (0, _assert2.default)(items, `_updateModelItem: ${itemsKey} not found`); let updated = false; items.value.properties.forEach(prop => { if (_jscodeshift2.default.Property.check(prop) && _check(prop.key, name)) { updated = true; prop.value = utils.getExpression(source); } }); (0, _assert2.default)(updated, `_updateModelItem: ${itemsKey}.${name} not found`); }); }, removeReducer(name) { this._removeModelItem(name, { itemsKey: 'reducers' }); }, removeEffect(name) { this._removeModelItem(name, { itemsKey: 'effects' }); }, removeSubscription(name) { this._removeModelItem(name, { itemsKey: 'subscriptions' }); }, /** * @private */ _removeModelItem(name, { itemsKey }) { return this.forEach(path => { let items = null; path.node.properties.forEach(prop => { if (_jscodeshift2.default.Property.check(prop) && prop.key.name === itemsKey) { (0, _assert2.default)(_jscodeshift2.default.ObjectExpression.check(prop.value), `_removeModelItem: ${itemsKey} should be ObjectExpression, but got ${prop.value.type}`); items = prop; } }); (0, _assert2.default)(items, `_removeModelItem: ${itemsKey} not found`); let removed = false; items.value.properties = items.value.properties.filter(prop => { if (_jscodeshift2.default.Property.check(prop) && _check(prop.key, name)) { removed = true; return false; } return true; }); (0, _assert2.default)(removed, `_removeModelItem: ${itemsKey}.${name} not found`); }); }, getModelInfo() { const defaultModel = { reducers: [], effects: [], subscriptions: [] }; return this.simpleMap(path => { const node = path.node; const result = node.properties.reduce((memo, prop) => { const name = prop.key.name; switch (name) { case 'namespace': memo[name] = prop.value.value; return memo; case 'state': memo[name] = utils.recursiveParse(prop.value); return memo; case 'reducers': memo[name] = parseReducers(prop.value); return memo; case 'effects': memo[name] = parseEffects(prop.value); return memo; case 'subscriptions': memo[name] = parseSubscriptions(prop.value); return memo; default: throw new Error(`getModelProperties: unrecognized property of dva model: ${name}`); } }, defaultModel); // TODO: reducers 等的解析支持 VariableDeclaraction // TODO: reducers 等的解析支持通过 import 从外部引入 // TODO: add id for reducers, effects and subscriptions return result; }); function parseBasic(node, parseType, extra) { (0, _assert2.default)(node.type === 'ObjectExpression', `getModelProperties: ${parseType} should be ObjectExpression, but got ${node.type}`); return node.properties.map(prop => { const name = utils.getPropertyValue(prop.key); const result = { name, source: (0, _jscodeshift2.default)(prop.value).toSource() }; if (extra) { extra(result, prop.value); } return result; }); } function parseReducers(node) { return parseBasic(node, 'reducers'); } function parseEffects(node) { return parseBasic(node, 'effects', (result, node) => { result.dispatches = (0, _jscodeshift2.default)(node).findDispatchCalls().getActionTypeFromCall(); }); } function parseSubscriptions(node) { return parseBasic(node, 'subscriptions', (result, node) => { result.dispatches = (0, _jscodeshift2.default)(node).findDispatchCalls().getActionTypeFromCall(); }); } } }; function register(jscodeshift = _jscodeshift2.default) { jscodeshift.registerMethods(methods); } exports.default = { register: (0, _lodash2.default)(register) }; module.exports = exports['default'];