saven
Version:
1,010 lines • 57.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const t = require("babel-types");
const utils_1 = require("./utils");
const lodash_1 = require("lodash");
const jsx_1 = require("./jsx");
const constant_1 = require("./constant");
const babel_generator_1 = require("babel-generator");
const adapter_1 = require("./adapter");
const template = require('babel-template');
const calleeId = utils_1.incrementId();
function findParents(path, cb) {
const parents = [];
// tslint:disable-next-line:no-conditional-assignment
while (path = path.parentPath) {
if (cb(path)) {
parents.push(path);
}
}
return parents;
}
function isClassDcl(p) {
return p.isClassExpression() || p.isClassDeclaration();
}
function isChildrenOfJSXAttr(p) {
return !!p.findParent(p => p.isJSXAttribute());
}
function isContainStopPropagation(path) {
let matched = false;
if (path) {
path.traverse({
Identifier(p) {
if (p.node.name === 'stopPropagation' &&
p.parentPath.parentPath.isCallExpression()) {
matched = true;
}
}
});
}
return matched;
}
function buildAssignState(pendingState) {
return t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('Object'), t.identifier('assign')), [
t.memberExpression(t.thisExpression(), t.identifier('state')),
pendingState
]));
}
class RenderParser {
constructor(renderPath, methods, initState, referencedIdentifiers, usedState, loopStateName, customComponentNames, customComponentData, componentProperies) {
this.classProperties = new Set();
this.templates = new Map();
this.jsxDeclarations = new Set();
this.loopScopes = new Set();
this.returnedPaths = [];
this.usedThisState = new Set();
this.loopComponents = new Map();
this.loopRefIdentifiers = new Map();
this.reserveStateWords = new Set(['state', 'props']);
this.topLevelIfStatement = new Set();
this.usedEvents = new Set();
this.loopCalleeId = new Set();
this.handleJSXElement = (jsxElementPath, func) => {
const parentNode = jsxElementPath.parent;
const parentPath = jsxElementPath.parentPath;
const isJSXChildren = t.isJSXElement(parentNode);
if (!isJSXChildren) {
let statementParent = jsxElementPath.getStatementParent();
const isReturnStatement = statementParent.isReturnStatement();
const isFinalReturn = statementParent.getFunctionParent().isClassMethod();
if (!(statementParent.isVariableDeclaration() ||
statementParent.isExpressionStatement())) {
statementParent = statementParent.findParent(s => s.isVariableDeclaration() || s.isExpressionStatement());
}
if (t.isVariableDeclarator(parentNode)) {
if (statementParent) {
const name = utils_1.findIdentifierFromStatement(statementParent.node);
// setTemplate(name, path, templates)
name && this.templates.set(name, jsxElementPath.node);
}
}
func({ parentNode, parentPath, statementParent, isReturnStatement, isFinalReturn });
}
};
this.replaceIdWithTemplate = (handleRefId = false) => (path) => {
if (!t.isJSXAttribute(path.parent)) {
path.traverse({
Identifier: (path) => {
const parentPath = path.parentPath;
if (parentPath.isConditionalExpression() ||
parentPath.isLogicalExpression() ||
path.isReferencedIdentifier()) {
const name = path.node.name;
if (handleRefId && Object.keys(this.renderScope.getAllBindings()).includes(name)) {
this.addRefIdentifier(path, path.node);
// referencedIdentifiers.add(path.node)
}
if (this.templates.has(name)) {
path.replaceWith(this.templates.get(name));
}
}
}
});
}
};
this.loopComponentVisitor = {
VariableDeclarator(path) {
const id = path.get('id');
const init = path.get('init');
const parentPath = path.parentPath;
if (id.isObjectPattern() &&
init.isThisExpression() &&
parentPath.isVariableDeclaration()) {
const { properties } = id.node;
const declareState = template('const state = this.state;')();
if (properties.length > 1) {
const index = properties.findIndex(p => t.isObjectProperty(p) && t.isIdentifier(p.key, { name: 'state' }));
properties.splice(index, 1);
parentPath.insertAfter(declareState);
}
else {
parentPath.insertAfter(declareState);
parentPath.remove();
}
}
},
JSXExpressionContainer: this.replaceIdWithTemplate(),
JSXElement: {
enter: (jsxElementPath) => {
this.handleJSXElement(jsxElementPath, (options) => {
this.handleConditionExpr(options, jsxElementPath);
});
},
exit: (jsxElementPath) => {
this.handleJSXElement(jsxElementPath, ({ parentNode, parentPath, statementParent, isFinalReturn }) => {
if (statementParent && statementParent.findParent(p => p === this.renderPath)) {
this.jsxDeclarations.add(statementParent);
}
if (t.isReturnStatement(parentNode)) {
if (!isFinalReturn) {
const callExpr = parentPath.findParent(p => p.isCallExpression());
if (callExpr.isCallExpression()) {
const callee = callExpr.node.callee;
if (this.loopComponents.has(callExpr)) {
return;
}
if (t.isMemberExpression(callee) &&
t.isIdentifier(callee.property) &&
callee.property.name === 'map') {
let ary = callee.object;
if (t.isCallExpression(ary) || utils_1.isContainFunction(callExpr.get('callee').get('object'))) {
const variableName = `${constant_1.LOOP_CALLEE}_${calleeId()}`;
callExpr.getStatementParent().insertBefore(utils_1.buildConstVariableDeclaration(variableName, ary));
ary = t.identifier(variableName);
}
if (t.isMemberExpression(ary)) {
const id = utils_1.findFirstIdentifierFromMemberExpression(ary);
if (t.isIdentifier(id)) {
this.referencedIdentifiers.add(id);
}
}
else if (t.isIdentifier(ary)) {
const parentCallExpr = callExpr.find(p => p.isCallExpression());
if (!utils_1.isArrayMapCallExpression(parentCallExpr) && parentCallExpr !== callExpr) {
this.referencedIdentifiers.add(ary);
}
}
jsx_1.setJSXAttr(jsxElementPath.node, 'wx:for', t.jSXExpressionContainer(ary));
this.loopCalleeId.add(utils_1.findFirstIdentifierFromMemberExpression(callee));
const [func] = callExpr.node.arguments;
if (t.isFunctionExpression(func) ||
t.isArrowFunctionExpression(func)) {
const [item, index] = func.params;
if (t.isIdentifier(item)) {
jsx_1.setJSXAttr(jsxElementPath.node, 'wx:for-item', t.stringLiteral(item.name));
this.loopScopes.add(item.name);
}
else if (t.isObjectPattern(item)) {
throw utils_1.codeFrameError(item.loc, 'JSX map 循环参数暂时不支持使用 Object pattern 解构。');
}
else {
jsx_1.setJSXAttr(jsxElementPath.node, 'wx:for-item', t.stringLiteral('__item'));
}
if (t.isIdentifier(index)) {
jsx_1.setJSXAttr(jsxElementPath.node, 'wx:for-index', t.stringLiteral(index.name));
this.loopScopes.add(index.name);
}
this.loopComponents.set(callExpr, jsxElementPath);
// caller.replaceWith(jsxElementPath.node)
if (statementParent) {
const name = utils_1.findIdentifierFromStatement(statementParent.node);
// setTemplate(name, path, templates)
name && this.templates.set(name, jsxElementPath.node);
}
}
}
}
}
}
else if (t.isArrowFunctionExpression(parentNode)) {
parentPath.replaceWith(t.arrowFunctionExpression(parentNode.params, t.blockStatement([
t.returnStatement(jsxElementPath.node)
])));
}
});
}
}
};
this.jsxElementVisitor = {
JSXElement: (jsxElementPath) => {
this.handleJSXElement(jsxElementPath, (options) => {
const { parentNode, parentPath, isFinalReturn } = options;
this.handleConditionExpr(options, jsxElementPath);
// this.jsxDeclarations.add(statementParent)
/**
* @TODO
* 有空做一个 TS 的 pattern matching 函数
* 把分支重构出来复用
*/
if (t.isReturnStatement(parentNode)) {
if (!isFinalReturn) {
//
}
else {
const ifStatement = parentPath.findParent(p => p.isIfStatement());
const blockStatement = parentPath.findParent(p => p.isBlockStatement() && (p.parentPath === ifStatement));
if (blockStatement && blockStatement.isBlockStatement()) {
blockStatement.traverse({
VariableDeclarator: (p) => {
const { id, init } = p.node;
const ifStem = p.parentPath.parentPath.parentPath;
if (!ifStem.isIfStatement() || utils_1.isContainJSXElement(p)) {
return;
}
if (t.isIdentifier(id)) {
if (id.name.startsWith('loopArray')) {
this.renderPath.node.body.body.unshift(t.variableDeclaration('let', [t.variableDeclarator(t.identifier(id.name))]));
p.parentPath.replaceWith(template('ID = INIT;')({ ID: t.identifier(id.name), INIT: init }));
}
else {
const newId = this.renderScope.generateDeclaredUidIdentifier('$' + id.name);
blockStatement.scope.rename(id.name, newId.name);
p.parentPath.replaceWith(template('ID = INIT;')({ ID: newId, INIT: init }));
}
}
}
});
}
const block = this.finalReturnElement || jsx_1.buildBlockElement();
if (utils_1.isBlockIfStatement(ifStatement, blockStatement)) {
const { test, alternate, consequent } = ifStatement.node;
// blockStatement.node.body.push(t.returnStatement(
// t.memberExpression(t.thisExpression(), t.identifier('state'))
// ))
if (alternate === blockStatement.node) {
throw utils_1.codeFrameError(parentNode.loc, '不必要的 else 分支,请遵从 ESLint consistent-return: https://eslint.org/docs/rules/consistent-return');
}
else if (consequent === blockStatement.node) {
const parentIfStatement = ifStatement.findParent(p => p.isIfStatement());
if (parentIfStatement) {
jsx_1.setJSXAttr(jsxElementPath.node, 'wx:elif', t.jSXExpressionContainer(test), jsxElementPath);
}
else {
if (this.topLevelIfStatement.size > 0) {
jsx_1.setJSXAttr(jsxElementPath.node, 'wx:elif', t.jSXExpressionContainer(test), jsxElementPath);
}
else {
utils_1.newJSXIfAttr(jsxElementPath.node, test, jsxElementPath);
this.topLevelIfStatement.add(ifStatement);
}
}
}
}
else if (block.children.length !== 0) {
jsx_1.setJSXAttr(jsxElementPath.node, 'wx:else');
}
block.children.push(jsxElementPath.node);
this.finalReturnElement = block;
this.returnedPaths.push(parentPath);
}
}
else if (t.isArrowFunctionExpression(parentNode)) {
// console.log('arrow')
}
else if (t.isAssignmentExpression(parentNode)) {
if (t.isIdentifier(parentNode.left)) {
const name = parentNode.left.name;
const bindingNode = this.renderScope.getOwnBinding(name).path.node;
const block = this.templates.get(name) || jsx_1.buildBlockElement();
if (utils_1.isEmptyDeclarator(bindingNode)) {
const ifStatement = parentPath.findParent(p => p.isIfStatement());
const blockStatement = parentPath.findParent(p => p.isBlockStatement());
if (utils_1.isBlockIfStatement(ifStatement, blockStatement)) {
const { test, alternate, consequent } = ifStatement.node;
if (alternate === blockStatement.node) {
jsx_1.setJSXAttr(jsxElementPath.node, 'wx:else');
}
else if (consequent === blockStatement.node) {
const parentIfStatement = ifStatement.findParent(p => p.isIfStatement());
if (parentIfStatement && parentIfStatement.get('alternate') === ifStatement) {
jsx_1.setJSXAttr(jsxElementPath.node, 'wx:elif', t.jSXExpressionContainer(test), jsxElementPath);
}
else {
if (parentIfStatement) {
utils_1.newJSXIfAttr(block, parentIfStatement.node.test, jsxElementPath);
}
utils_1.newJSXIfAttr(jsxElementPath.node, test, jsxElementPath);
}
}
block.children.push(jsxElementPath.node);
// setTemplate(name, path, templates)
name && this.templates.set(name, block);
}
}
else {
throw utils_1.codeFrameError(jsxElementPath.node.loc, '请将 JSX 赋值表达式初始化为 null,然后再进行 if 条件表达式赋值。');
}
}
}
else if (!t.isJSXElement(parentNode)) {
// throwError(path, '考虑只对 JSX 元素赋值一次。')
}
});
// handle jsx attrs
jsxElementPath.traverse(this.jsxAttrVisitor);
}
};
this.jsxAttrVisitor = {
JSXExpressionContainer: (path) => {
if (!isChildrenOfJSXAttr(path)) {
return;
}
const expression = path.get('expression');
if (expression.isStringLiteral()) {
path.replaceWith(expression);
}
else if (expression.isCallExpression()) {
const node = expression.node;
if (t.isMemberExpression(node.callee) &&
t.isIdentifier(node.callee.property) &&
node.callee.property.name === 'bind') {
const JSXElement = path.findParent(p => p.isJSXElement())
.node;
// const JSXAttribute = path.findParent(p => p.isJSXAttribute())
let bindCalleeName = null;
if (t.isIdentifier(node.callee.object)) {
bindCalleeName = node.callee.object.name;
}
else if (t.isMemberExpression(node.callee.object)) {
if (t.isIdentifier(node.callee.object.property)) {
bindCalleeName = node.callee.object.property.name;
}
}
if (bindCalleeName !== null) {
const args = expression.get('arguments');
args.forEach((arg, index) => {
const node = arg.node;
const argName = babel_generator_1.default(node).code;
if (index === 0) {
jsx_1.setJSXAttr(JSXElement, `data-e-${bindCalleeName}-so`, t.stringLiteral(argName));
}
else {
let expr = null;
if (t.isIdentifier(node) && path.scope.hasBinding(argName)) {
this.addRefIdentifier(path, node);
expr = t.jSXExpressionContainer(node);
}
else if (node.type === 'NumericLiteral' || t.isStringLiteral(node) || t.isBooleanLiteral(node) || t.isNullLiteral(node)) {
expr = t.jSXExpressionContainer(node);
}
else if (utils_1.hasComplexExpression(arg)) {
const id = utils_1.generateAnonymousState(this.renderScope, arg, this.referencedIdentifiers);
expr = t.jSXExpressionContainer(id);
}
else {
expr = t.jSXExpressionContainer(t.identifier(argName));
}
jsx_1.setJSXAttr(JSXElement, `data-e-${bindCalleeName}-a-${utils_1.toLetters(index)}`, expr);
}
});
expression.replaceWith(t.stringLiteral(`${bindCalleeName}`));
}
}
}
},
JSXAttribute: (path) => {
const { name, value } = path.node;
let eventShouldBeCatched = false;
const jsxElementPath = path.parentPath.parentPath;
if (t.isJSXIdentifier(name) && jsxElementPath.isJSXElement()) {
const componentName = jsxElementPath.node.openingElement.name.name;
if (name.name === 'key') {
if (constant_1.THIRD_PARTY_COMPONENTS.has(componentName)) {
return;
}
const jsx = path.findParent(p => p.isJSXElement());
const loopBlock = jsx.findParent(p => {
if (p.isJSXElement()) {
const element = p.get('openingElement');
if (element.get('name').isJSXIdentifier({ name: 'block' })) {
const attrs = element.node.attributes;
const hasWXForLoop = attrs.some(attr => t.isJSXIdentifier(attr.name, { name: 'wx:for' }));
const hasWXKey = attrs.some(attr => t.isJSXIdentifier(attr.name, { name: 'wx:key' }));
return hasWXForLoop && !hasWXKey;
}
}
return false;
});
if (loopBlock) {
jsx_1.setJSXAttr(loopBlock.node, 'wx:key', value);
path.remove();
}
else {
path.get('name').replaceWith(t.jSXIdentifier('wx:key'));
}
}
else if (name.name.startsWith('on')) {
if (t.isJSXExpressionContainer(value)) {
const methodName = utils_1.findMethodName(value.expression);
methodName && this.usedEvents.add(methodName);
const method = this.methods.get(methodName);
// if (method && t.isIdentifier(method.node.key)) {
// this.usedEvents.add(methodName)
// } else if (method === null) {
// this.usedEvents.add(methodName)
// }
if (!babel_generator_1.default(value.expression).code.includes('.bind')) {
path.node.value = t.stringLiteral(`${methodName}`);
}
if (this.methods.has(methodName)) {
eventShouldBeCatched = isContainStopPropagation(method);
}
const componentName = jsxElementPath.node.openingElement.name;
if (t.isJSXIdentifier(componentName) && !constant_1.DEFAULT_Component_SET.has(componentName.name)) {
const element = path.parent;
if (process.env.NODE_ENV !== 'test') {
const fnName = `__fn_${name.name}`;
element.attributes = element.attributes.concat([t.jSXAttribute(t.jSXIdentifier(fnName))]);
}
}
}
if (t.isJSXIdentifier(jsxElementPath.node.openingElement.name)) {
const componentName = jsxElementPath.node.openingElement.name.name;
if (constant_1.DEFAULT_Component_SET.has(componentName)) {
let transformName = `${eventShouldBeCatched ? 'catch' : 'bind'}`
+ name.name.slice(2).toLowerCase();
if (name.name === 'onClick') {
transformName = eventShouldBeCatched ? 'catchtap' : 'bindtap';
}
path.node.name = t.jSXIdentifier(transformName);
}
else if (constant_1.THIRD_PARTY_COMPONENTS.has(componentName)) {
path.node.name = t.jSXIdentifier('bind' + name.name.slice(2));
}
else {
path.node.name = t.jSXIdentifier('bind' + name.name.toLowerCase());
}
}
// let transformName = `${eventShouldBeCatched ? 'catch' : 'bind'}` + name.name.slice(2, name.name.length)
// transformName = eventShouldBeCatched
// ? CATCH_EVENT_MAP.get(name.name)!
// : BIND_EVENT_MAP.get(name.name)!
}
}
},
Identifier: (path) => {
if (!isChildrenOfJSXAttr(path)) {
return;
}
if (!path.isReferencedIdentifier()) {
return;
}
const parentPath = path.parentPath;
if (parentPath.isConditionalExpression() ||
parentPath.isLogicalExpression() ||
parentPath.isJSXExpressionContainer() ||
this.renderScope.hasOwnBinding(path.node.name)) {
this.addRefIdentifier(path, path.node);
}
},
MemberExpression: (path) => {
if (!isChildrenOfJSXAttr(path)) {
return;
}
if (!path.isReferencedMemberExpression() || path.parentPath.isMemberExpression()) {
return;
}
const { object, property } = path.node;
if (t.isMemberExpression(object) &&
t.isThisExpression(object.object) &&
t.isIdentifier(object.property, { name: 'state' })) {
if (t.isIdentifier(property)) {
this.usedThisState.add(property.name);
}
else if (t.isMemberExpression(property)) {
const id = utils_1.findFirstIdentifierFromMemberExpression(property);
if (id && this.renderScope.hasBinding(id.name)) {
this.usedThisState.add(id.name);
}
}
return;
}
const code = babel_generator_1.default(path.node).code;
if (code.includes('this.$router.params') && t.isIdentifier(property)) {
const name = this.renderScope.generateUid(property.name);
const dcl = utils_1.buildConstVariableDeclaration(name, path.node);
this.renderPath.node.body.body.unshift(dcl);
path.replaceWith(t.identifier(name));
}
const parentPath = path.parentPath;
const id = utils_1.findFirstIdentifierFromMemberExpression(path.node);
if (t.isThisExpression(id)) {
return;
}
if (parentPath.isConditionalExpression() ||
parentPath.isLogicalExpression() ||
parentPath.isJSXExpressionContainer() ||
(this.renderScope.hasOwnBinding(id.name))) {
this.addRefIdentifier(path, id);
}
},
ArrowFunctionExpression: (path) => {
if (!isChildrenOfJSXAttr(path)) {
return;
}
const uid = path.scope.generateUid('_anonymous_function_');
const c = t.classProperty(t.identifier(uid), path.node);
this.classProperties.add(c);
}
};
this.visitors = Object.assign({ VariableDeclarator(path) {
const init = path.get('init');
const ifStem = init.findParent(p => p.isIfStatement());
if (ifStem && init.node === null) {
init.replaceWith(t.identifier('undefined'));
}
},
JSXEmptyExpression(path) {
const parent = path.parentPath;
if (path.parentPath.isJSXExpressionContainer()) {
parent.remove();
}
},
NullLiteral(path) {
const statementParent = path.getStatementParent();
if (statementParent && statementParent.isReturnStatement() && !t.isBinaryExpression(path.parent) && !isChildrenOfJSXAttr(path)) {
path.replaceWith(t.jSXElement(t.jSXOpeningElement(t.jSXIdentifier('View'), []), undefined, [], true));
}
}, ReturnStatement: (path) => {
const parentPath = path.parentPath;
if (parentPath.parentPath.isClassMethod() ||
(parentPath.parentPath.isIfStatement() && parentPath.parentPath.parentPath.isClassMethod())) {
this.replaceIdWithTemplate()(path);
}
} }, this.jsxElementVisitor, { JSXExpressionContainer: this.replaceIdWithTemplate(true) });
/**
* jsxDeclarations,
* renderScope,
* methods,
* loopScopes,
* initState,
* templates
*/
this.handleLoopComponents = () => {
const loopArrayId = utils_1.incrementId();
const replaceQueue = [];
this.loopComponents.forEach((component, callee) => {
for (const dcl of this.jsxDeclarations) {
const isChildren = dcl && dcl.findParent(d => d === callee);
if (isChildren) {
this.jsxDeclarations.delete(dcl);
dcl.remove();
}
}
const blockStatementPath = component.findParent(p => p.isBlockStatement());
const body = blockStatementPath.node.body;
let hasOriginalRef = false;
let stateToBeAssign = new Set(lodash_1.difference(Object.keys(blockStatementPath.scope.getAllBindings()), Object.keys(this.renderScope.getAllBindings())).filter(i => {
return !this.methods.has(i);
})
.filter(i => !this.loopScopes.has(i))
.filter(i => !this.initState.has(i))
.filter(i => !this.templates.has(i))
.filter(i => !i.includes('.'))
.filter(i => i !== constant_1.MAP_CALL_ITERATOR));
if (body.length > 1) {
const [func] = callee.node.arguments;
if (t.isFunctionExpression(func) || t.isArrowFunctionExpression(func)) {
const [item] = func.params;
const parents = findParents(callee, (p) => utils_1.isArrayMapCallExpression(p));
const iterators = new Set([item.name, ...parents
.map((p) => lodash_1.get(p, 'node.arguments[0].params[0].name', ''))
.filter(Boolean)]);
for (const [index, statement] of body.entries()) {
if (t.isVariableDeclaration(statement)) {
for (const dcl of statement.declarations) {
if (t.isIdentifier(dcl.id)) {
const name = dcl.id.name;
if (name.startsWith(constant_1.LOOP_STATE) ||
name.startsWith(constant_1.LOOP_CALLEE)) {
stateToBeAssign.add(name);
dcl.id = t.identifier(name);
}
}
}
}
if (t.isReturnStatement(statement)) {
body.splice(index, 1);
}
}
stateToBeAssign.forEach(s => this.loopRefIdentifiers.set(s, callee));
const properties = Array.from(stateToBeAssign).map(state => t.objectProperty(t.identifier(state), t.identifier(state)));
// tslint:disable-next-line:no-inner-declarations
function replaceOriginal(path, parent, name) {
if (path.isReferencedIdentifier() &&
iterators.has(name) &&
!(t.isMemberExpression(parent) && t.isIdentifier(parent.property, { name: constant_1.LOOP_ORIGINAL })) &&
!(t.isMemberExpression(parent) && t.isIdentifier(parent.property) && (parent.property.name.startsWith(constant_1.LOOP_STATE) || parent.property.name.startsWith(constant_1.LOOP_CALLEE)))) {
path.replaceWith(t.memberExpression(t.identifier(name), t.identifier(constant_1.LOOP_ORIGINAL)));
hasOriginalRef = true;
}
}
const bodyPath = callee.get('arguments')[0].get('body');
bodyPath.traverse({
Identifier(path) {
const name = path.node.name;
const parent = path.parent;
replaceOriginal(path, parent, name);
}
});
const replacements = new Set();
component.traverse({
Identifier: (path) => {
const name = path.node.name;
const parent = path.parent;
if (replacements.has(parent) || this.loopCalleeId.has(path.node)) {
return;
}
if (stateToBeAssign.has(name) && path.isReferencedIdentifier()) {
const replacement = t.memberExpression(t.identifier(item.name), path.node);
path.replaceWith(replacement);
replacements.add(replacement);
hasOriginalRef = true;
}
else {
replaceOriginal(path, parent, name);
}
},
MemberExpression: (path) => {
const id = utils_1.findFirstIdentifierFromMemberExpression(path.node);
if (stateToBeAssign.has(id.name) && !this.loopCalleeId.has(id)) {
path.node.object = t.identifier(item.name + '.' + id.name);
}
}
});
if (hasOriginalRef) {
const originalProp = t.objectProperty(t.identifier(constant_1.LOOP_ORIGINAL), t.memberExpression(t.identifier(item.name), t.identifier(constant_1.LOOP_ORIGINAL)));
properties.push(originalProp);
body.unshift(t.expressionStatement(t.assignmentExpression('=', t.identifier(item.name), t.objectExpression([
t.objectProperty(t.identifier(constant_1.LOOP_ORIGINAL), t.callExpression(t.identifier(constant_1.INTERNAL_GET_ORIGNAL), [t.identifier(item.name)]))
]))));
}
const returnStatement = t.returnStatement(properties.length ? t.objectExpression(properties) : item);
const parentCallee = callee.findParent(c => utils_1.isArrayMapCallExpression(c));
if (utils_1.isArrayMapCallExpression(parentCallee)) {
const [func] = parentCallee.node.arguments;
const { object } = callee.node.callee;
if (t.isFunctionExpression(func) || t.isArrowFunctionExpression(func)) {
const funcBody = func.body;
if (t.isBlockStatement(funcBody)) {
if (t.isIdentifier(object)) {
if (this.loopCalleeId.has(object)) {
const variableName = `${constant_1.LOOP_CALLEE}_${calleeId()}`;
funcBody.body.splice(funcBody.body.length - 1, 0, utils_1.buildConstVariableDeclaration(variableName, callee.node));
const iterator = func.params[0];
component.node.openingElement.attributes.forEach(attr => {
if (attr.name.name === adapter_1.Adapter.for && t.isIdentifier(iterator)) {
attr.value = t.jSXExpressionContainer(t.memberExpression(iterator, t.identifier(variableName)));
}
});
}
else {
funcBody.body.splice(funcBody.body.length - 1, 0, t.expressionStatement(t.assignmentExpression('=', object, callee.node)));
}
}
else if (t.isMemberExpression(object)) {
funcBody.body.splice(funcBody.body.length - 1, 0, t.expressionStatement(t.assignmentExpression('=', object, callee.node)));
}
else {
throw utils_1.codeFrameError(object.loc, '请简化该表达式为标识符或成员表达式');
}
}
}
body.push(returnStatement);
}
else {
body.push(returnStatement);
const stateName = 'loopArray' + loopArrayId();
this.loopStateName.forEach((newName, callExpr) => {
if (callExpr === callee) {
const classBody = this.renderPath.parent;
for (const property of classBody.body) {
if (t.isClassProperty(property) && property.key.name === '$dynamicComponents') {
const objects = property.value;
for (const objProp of objects.properties) {
if (t.isObjectProperty(objProp) && t.isIdentifier(objProp.key, { name: newName })) {
const func = objProp.value;
func.body.body[0] = utils_1.buildConstVariableDeclaration('stateName', t.stringLiteral(stateName));
}
}
}
}
}
});
// setJSXAttr(returned, 'wx:for', t.identifier(stateName))
this.addRefIdentifier(callee, t.identifier(stateName));
// this.referencedIdentifiers.add(t.identifier(stateName))
jsx_1.setJSXAttr(component.node, 'wx:for', t.jSXExpressionContainer(t.identifier(stateName)));
const returnBody = this.renderPath.node.body.body;
const ifStem = callee.findParent(p => p.isIfStatement());
// @TEST
if (ifStem && ifStem.isIfStatement()) {
const consequent = ifStem.get('consequent');
if (consequent.isBlockStatement()) {
const assignment = t.expressionStatement(t.assignmentExpression('=', t.identifier(stateName), utils_1.setParentCondition(component, callee.node, true)));
returnBody.unshift(t.variableDeclaration('let', [t.variableDeclarator(t.identifier(stateName))]));
consequent.node.body.push(assignment);
}
}
else {
const decl = utils_1.buildConstVariableDeclaration(stateName, utils_1.setParentCondition(component, callee.node, true));
returnBody.push(decl);
}
}
}
}
replaceQueue.push(() => {
const statement = component.getStatementParent();
callee.replaceWith(statement.isReturnStatement()
? statement.get('argument').node
: component.node);
});
});
replaceQueue.forEach(func => func());
};
this.setReserveWord = (word) => {
const binding = this.renderScope.getOwnBinding(word);
let hasStateId = false;
if (binding) {
const path = binding.path;
const id = path.get('id');
const init = path.get('init');
if (init.isThisExpression()) {
return hasStateId;
}
if (id.isObjectPattern()) {
hasStateId = id.node.properties.some(p => {
return (t.isObjectProperty(p) && t.isIdentifier(p.key, { name: word }))
|| (t.isRestProperty(p) && t.isIdentifier(p.argument, { name: word }));
});
}
else if (id.isIdentifier({ name: word })) {
hasStateId = true;
}
if (hasStateId) {
this.referencedIdentifiers.add(t.identifier(word));
}
}
if (hasStateId) {
this.reserveStateWords.delete(word);
}
};
this.renderPath = renderPath;
this.methods = methods;
this.initState = initState;
this.referencedIdentifiers = referencedIdentifiers;
this.loopStateName = loopStateName;
this.usedState = usedState;
this.customComponentNames = customComponentNames;
this.customComponentData = customComponentData;
this.componentProperies = componentProperies;
const renderBody = renderPath.get('body');
this.renderScope = renderBody.scope;
const [, error] = renderPath.node.body.body.filter(s => t.isReturnStatement(s));
if (error) {
throw utils_1.codeFrameError(error.loc, 'render 函数顶级作用域暂时只支持一个 return');
}
renderBody.traverse(this.loopComponentVisitor);
this.handleLoopComponents();
renderBody.traverse(this.visitors);
this.setOutputTemplate();
this.removeJSXStatement();
this.setUsedState();
this.setPendingState();
this.setCustomEvent();
this.createData();
this.setProperies();
}
handleConditionExpr({ parentNode, parentPath, statementParent }, jsxElementPath) {
if (t.isLogicalExpression(parentNode)) {
const { left, operator, right } = parentNode;
const leftExpression = parentPath.get('left');
if (operator === '&&' && t.isExpression(left)) {
if (utils_1.hasComplexExpression(leftExpression)) {
utils_1.generateAnonymousState(this.renderScope, leftExpression, this.referencedIdentifiers, true);
}
const block = jsx_1.buildBlockElement();
utils_1.newJSXIfAttr(block, leftExpression.node);
block.children = [jsxElementPath.node];
parentPath.replaceWith(block);
if (statementParent) {
const name = utils_1.findIdentifierFromStatement(statementParent.node);
utils_1.setTemplate(name, jsxElementPath, this.templates);
// name && templates.set(name, path.node)
}
}
if (operator === '||' && t.isExpression(left)) {
const newNode = t.conditionalExpression(left, left, right);
parentPath.replaceWith(newNode);
// this.handleConditionExpr({ parentNode: newNode, parentPath, statementParent }, jsxElementPath)
}
}
else if (t.isConditionalExpression(parentNode)) {
const { consequent, alternate } = parentNode;
const testExpression = parentPath.get('test');
const block = jsx_1.buildBlockElement();
if (utils_1.hasComplexExpression(testExpression)) {
utils_1.generateAnonymousState(parentPath.scope, testExpression, this.referencedIdentifiers, true);
}
const test = testExpression.node;
if (t.isJSXElement(consequent) && t.isLiteral(alternate)) {
const { value, confident } = parentPath.get('alternate').evaluate();
if (confident && !value) {
utils_1.newJSXIfAttr(block, test);
block.children = [jsxElementPath.node];
// newJSXIfAttr(jsxElementPath.node, test)
parentPath.replaceWith(block);
if (statementParent) {
const name = utils_1.findIdentifierFromStatement(statementParent.node);
utils_1.setTemplate(name, jsxElementPath, this.templates);
// name && templates.set(name, path.node)
}
}
}
else if (t.isLiteral(consequent) && t.isJSXElement(alternate)) {
if (t.isNullLiteral(consequent)) {
utils_1.newJSXIfAttr(block, utils_1.reverseBoolean(test));
// newJSXIfAttr(jsxElementPath.node, reverseBoolean(test))
block.children = [jsxElementPath.node];
parentPath.replaceWith(block);
if (statementParent) {
const name = utils_1.findIdentifierFromStatement(statementParent.node);
utils_1.setTemplate(name, jsxElementPath, this.templates);
// name && templates.set(name, path.node)
}
}
}
else if (t.isJSXElement(consequent) && t.isJSXElement(alternate)) {
const block2 = jsx_1.buildBlockElement();
block.children = [consequent];
utils_1.newJSXIfAttr(block, test);
jsx_1.setJSXAttr(block2, 'wx:else');
block2.children = [alternate];
const parentBlock = jsx_1.buildBlockElement();
parentBlock.children = [block, block2];
parentPath.replaceWith(parentBlock);
if (statementParent) {
const name = utils_1.findIdentifierFromStatement(statementParent.node);
utils_1.setTemplate(name, jsxElementPath, this.templates);
}
}
else if ((t.isJSXElement(consequent) && t.isCallExpression(alternate))
||
(t.isJSXElement(alternate) && t.isCallExpression(consequent))) {
//
}
else {
block.children = [t.jSXExpressionContainer(consequent)];
utils_1.newJSXIfAttr(block, test);
const block2 = jsx_1.buildBlockElement();
jsx_1.setJSXAttr(block2, 'wx:else');
block2.children = [t.jSXExpressionContainer(alternate)];
const parentBlock = jsx_1.buildBlockElement();
parentBlock.children = [block, block2];
parentPath.replaceWith(parentBlock);
if (statementParent) {
const name = utils_1.findIdentifierFromStatement(statementParent.node);
utils_1.setTemplate(name, jsxElementPath, this.templates);
}
}
}
}
setProperies() {
const properties = [];
this.componentProperies.forEach((propName) => {
properties.push(t.objectProperty(t.stringLiteral(propName), t.nullLiteral()));
});
let classProp = t.classProperty(t.identifier('properties'), t.objectExpression(properties));
classProp.static = true;
const classPath = this.renderPath.findParent(isClassDcl);
classPath.node.body.body.unshift(classProp);
}
addRefIdentifier(path, id) {
const arrayMap = path.findParent(p => utils_1.isArrayMapCallExpression(p));
if (arrayMap && arrayMap.isCallExpression()) {
this.loopRefIdentifiers.set(id.name, arrayMap);
}
else {
id && this.referencedIdentifiers.add(id);
}
}
setOutputTemplate() {
this.outputTemplate = jsx_1.parseJSXElement(this.finalReturnElement);
}
removeJSXStatement() {
this.jsxDeclarations.forEach(d => d && d.remove());
this.returnedPaths.forEach((p) => {
const ifStem = p.findParent(_ => _.isIfStatement());
if (ifStem) {
const node = p.node;
if (t.isJSXElement(node.argument)) {
const jsx = node.argument;
if (jsx.children.length === 0 && jsx.openingElement.attributes.length === 0) {
node.argument = t.nullLiteral();
}
else {
p.remove();
}
}
else {
const isValid = p.get('argument').evaluateTruthy();
if (!isValid) {
node.argument = t.nullLiteral();
}
else {
p.remove();
}
}
}
else {
p.remove();
}
});
}
setCustomEvent() {
const classPath = this.renderPath.findParent(isClassDcl);
let classProp = t.classProperty(t.identifier('$$events'), t.arrayExpression(Array.from(this.usedEvents).map(s => t.stringLiteral(s)))); // babel 6 typing 没有 static
classProp.static = true;
classPath.node.body.body.unshift(classProp);
}
setUsedState() {
Array.from(this.reserveStateWords).forEach(this.setReserveWord);
const usedState = Array.from(new Set(Array.from(this.referencedIdentifiers)
.map(i => i.name)
.concat([...this.initState, ...this.usedThisState])))
.concat(...this.usedState)
// .filter(i => {
// return !methods.has(i)
// })
.filter(i => !this.loopScopes.has(i))
.filter(i => !this.templates.has(i));
const classPath = this.renderPath.findParent(isClassDcl);
classPath.node.body.body.unshift(t.classProperty(t.identifier('$usedState'), t.arrayExpression([...new Set(usedState
.filter(s => !this.loopScopes.has(s.split('.')[0]))
.filter(i => i !== constant_1.MAP_CALL_ITERATOR && !this.reserveStateWords.has(i))
.filter(i => utils_1.isVarName(i))
.filter(i => !this.loopRefIdentifiers.has(i))
.concat(Array.from(this.customComponentNames)))]
.map(s => t.stringLiteral(s)))));
}
setPendingState() {
let properties = Array.from(new Set(Array.from(this.referencedIdentifiers)
.map(i => i.name)))
.filter(i => {
return !this.methods.has(i);
})
.filter(i => !this.loopScopes.has(i))
.filter(i => !this.initState.has(i))
.filter(i => !this.templates.has(i))
.filter(i => utils_1.isVarName(i))
.filter(i => i !== constant_1.MAP_CALL_ITERATOR && !this.reserveStateWords.has(i))
.filter(i => !i.startsWith('$$'))
.filter(i => !this.loopRefIdentifiers.has(i))
.map(i => t.objectProperty(t.identifier(i), t.identifier(i)));
if (this.customComponentData.length > 0) {
properties = properties.concat(this.customComponentData);
}
const pendingState = t.objectExpression(properties);
this.renderPath.node.body.body = this.renderPath.node.body.body.concat(buildAssignState(pendingState), t.returnStatement(t.memberExpression(t.thisExpression(), t.identifier('state'))));
}
createData() {
const renderBody = this.renderPath.get('body');
renderBody.traverse({
ThisExpression(path) {
const property = path.getSibling('property');
if (property.isIdentifier({ name: 'state' })) {
property.replaceWith(t.identifier('__state'));
}
if (property.isIdentifier({ name: 'props' })) {
property.replaceWith(t.identifier('__props'));
}
}
});
this.renderPath.node.body.body.unshift(template(`this.__state = arguments[0] || this.state || {};`)(), template(`this.__props = arguments[1] || this.props || {};`)());
if (t.isIdentifier(this.renderPath.node.key)) {
this.renderPath.node.key.name = '_createData';
}
}
}
exports.RenderParser = RenderParser;
//# sourceMappingURL=render.js.map