[ t.thisExpression() ].concat(vars.map(function(v) { return t.conditionalExpression( t.binaryExpression('in', t.stringLiteral(v), t.logicalExpression('||', t.identifier(bag), t.objectExpression([]))), t.memberExpression(t.logicalExpression('||', t.identifier(bag), t.objectExpression([])), t.identifier(v)), t.conditionalExpression( t.binaryExpression('!==', t.unaryExpression('typeof', t.identifier(v)), t.stringLiteral('undefined')), t.identifier(v), t.identifier('undefined') ) ) }))
ast_buffer: function(ast) { return [t.expressionStatement( t.assignmentExpression('=', t.identifier('pug_html'), t.binaryExpression('+', t.identifier('pug_html'), ast) ))]; },
function simplifyCharCodeAt(path) { var node = path.node; var charCodeCallExprNode = (t.isCallExpression(node.left) && t.isMemberExpression(node.left.callee) && t.isIdentifier(node.left.callee.property) && node.left.callee.property.name === 'charCodeAt') ? node.left : (t.isCallExpression(node.right) && t.isMemberExpression(node.right.callee) && t.isIdentifier(node.right.callee.property) && node.right.callee.property.name === 'charCodeAt') ? node.right : false; if (charCodeCallExprNode === false) return node; var indexLiteralNode = charCodeCallExprNode.arguments[0]; if (!indexLiteralNode) return node; if (node.operator !== '===') return node; var asciiNode = t.valueToNode(String.fromCharCode( (charCodeCallExprNode === node.left ? node.right : node.left).value)); var identifierIndexMemberNode = t.memberExpression( charCodeCallExprNode.callee.object, indexLiteralNode, true ); return t.binaryExpression('===', identifierIndexMemberNode, asciiNode); }
beforeEach(function () { program = new Program(); context = new HindleyMilnerContext(null, new TypeInferenceContext(program)); rule = new ForStatementRefinementRule(); init = t.assignmentExpression("=", t.identifier("x"), t.numericLiteral(10)); test = t.binaryExpression("<", t.identifier("x"), t.numericLiteral(100)); update = t.updateExpression("++", t.identifier("x")); });
typeInferenceAnalysis.analyse = (node, typeEnvironment) => { ++analyseCount; const recursiveCall = t.callExpression(t.identifier("successor"), [t.binaryExpression("-", t.identifier("x"), t.numericLiteral(-1))]); typeInferenceAnalysis.infer.withArgs(recursiveCall.callee).returns(funcT); program.symbolTable.setSymbol(recursiveCall.arguments[0].left, x); rule.refine(recursiveCall, context); return new Map([[null, typeEnvironment.setType(Symbol.RETURN, NumberType.create())]]); };
const ifStatement = cases.map((Case, index) => { const [consequent] = Case.consequent; if (!t.isBlockStatement(consequent)) { throw utils_1.codeFrameError(switchStatement.node, '含有 JSX 的 switch case 语句必须每种情况都用花括号 `{}` 包裹结果'); } const block = t.blockStatement(consequent.body.filter(b => !t.isBreakStatement(b))); if (index !== cases.length - 1 && t.isNullLiteral(Case.test)) { throw utils_1.codeFrameError(Case, '含有 JSX 的 switch case 语句只有最后一个 case 才能是 default'); } const test = Case.test === null ? t.nullLiteral() : t.binaryExpression('===', discriminant, Case.test); return { block, test }; }).reduceRight((ifStatement, item) => {
buildHas(ret: { type: "Identifier" }, call: { type: "CallExpression" }) { let body = this.body; body.push(t.variableDeclaration("var", [ t.variableDeclarator(ret, call) ])); let retCheck; let has = this.has; let cases = []; if (has.hasReturn) { // typeof ret === "object" retCheck = buildRetCheck({ RETURN: ret }); } if (has.hasBreakContinue) { for (let key in has.map) { cases.push(t.switchCase(t.stringLiteral(key), [has.map[key]])); } if (has.hasReturn) { cases.push(t.switchCase(null, [retCheck])); } if (cases.length === 1) { let single = cases[0]; body.push(t.ifStatement( t.binaryExpression("===", ret, single.test), single.consequent[0] )); } else { if (this.loop) { // https://github.com/babel/babel/issues/998 for (let i = 0; i < cases.length; i++) { let caseConsequent = cases[i].consequent[0]; if (t.isBreakStatement(caseConsequent) && !caseConsequent.label) { caseConsequent.label = this.loopLabel = this.loopLabel || this.scope.generateUidIdentifier("loop"); } } } body.push(t.switchStatement(ret, cases)); } } else { if (has.hasReturn) { body.push(retCheck); } } }
function optimiseLengthGetter(path, argsLengthExpression, argsId, offset) { if (offset) { path.parentPath.replaceWith( t.binaryExpression( "-", argsLengthExpression, t.numericLiteral(offset), ) ); } else { path.replaceWith(argsId); } }
specHandle(path: NodePath) { let property; let computed; let args; let thisReference; let parent = path.parent; let node = path.node; if (isIllegalBareSuper(node, parent)) { throw path.buildCodeFrameError(messages.get("classesIllegalBareSuper")); } if (t.isCallExpression(node)) { let callee = node.callee; if (t.isSuper(callee)) { return; } else if (isMemberExpressionSuper(callee)) { // super.test(); -> _get(Object.getPrototypeOf(objectRef.prototype), "test", this).call(this); property = callee.property; computed = callee.computed; args = node.arguments; } } else if (t.isMemberExpression(node) && t.isSuper(node.object)) { // super.name; -> _get(Object.getPrototypeOf(objectRef.prototype), "name", this); property = node.property; computed = node.computed; } else if (t.isUpdateExpression(node) && isMemberExpressionSuper(node.argument)) { let binary = t.binaryExpression(node.operator[0], node.argument, t.numericLiteral(1)); if (node.prefix) { // ++super.foo; -> super.foo += 1; return this.specHandleAssignmentExpression(null, path, binary); } else { // super.foo++; -> let _ref = super.foo; super.foo = _ref + 1; let ref = path.scope.generateUidIdentifier("ref"); return this.specHandleAssignmentExpression(ref, path, binary).concat(t.expressionStatement(ref)); } } else if (t.isAssignmentExpression(node) && isMemberExpressionSuper(node.left)) { return this.specHandleAssignmentExpression(null, path, node); } if (!property) return; let superProperty = this.getSuperProperty(property, computed, thisReference); if (args) { return this.optimiseCall(superProperty, args); } else { return superProperty; } }
function optimiseIndexGetter(path, argsId, offset) { let index; if (t.isNumericLiteral(path.parent.property)) { index = t.numericLiteral(path.parent.property.value + offset); } else { index = t.binaryExpression("+", path.parent.property, t.numericLiteral(offset)); } path.parentPath.replaceWith(loadRest({ ARGUMENTS: argsId, INDEX: index, })); }
specHandleAssignmentExpression(ref, path, node) { if (node.operator === "=") { // super.name = "val"; -> _set(Object.getPrototypeOf(objectRef.prototype), "name", this); return this.setSuperProperty(node.left.property, node.right, node.left.computed); } else { // super.age += 2; -> let _ref = super.age; super.age = _ref + 2; ref = ref || path.scope.generateUidIdentifier("ref"); return [ t.variableDeclaration("var", [ t.variableDeclarator(ref, node.left) ]), t.expressionStatement( t.assignmentExpression("=", node.left, t.binaryExpression(node.operator[0], ref, node.right)) ) ]; } }
function transform(options) { if (options.adapter) { adapter_1.setAdapter(options.adapter); } if (adapter_1.Adapter.type === "swan" /* swan */) { constant_1.setLoopOriginal('privateOriginal'); } constant_1.THIRD_PARTY_COMPONENTS.clear(); const code = options.isTyped ? ts.transpile(options.code, { jsx: ts.JsxEmit.Preserve, target: ts.ScriptTarget.ESNext, importHelpers: true, noEmitHelpers: true }) : options.code; options.env = Object.assign({ 'process.env.TARO_ENV': options.adapter || 'weapp' }, options.env || {}); options_1.setTransformOptions(options); utils_1.setting.sourceCode = code; // babel-traverse 无法生成 Hub // 导致 Path#getSource|buildCodeFrameError 都无法直接使用 // 原因大概是 babylon.parse 没有生成 File 实例导致 scope 和 path 原型上都没有 `file` // 将来升级到 babel@7 可以直接用 parse 而不是 transform const ast = babel_core_1.transform(code, options_1.buildBabelTransformOptions()).ast; if (options.isNormal) { return { ast }; } // transformFromAst(ast, code) let result; const componentSourceMap = new Map(); const imageSource = new Set(); const importSources = new Set(); let componentProperies = []; let mainClass; let storeName; let renderMethod; let isImportTaro = false; babel_traverse_1.default(ast, { TemplateLiteral(path) { const nodes = []; const { quasis, expressions } = path.node; let index = 0; if (path.parentPath.isTaggedTemplateExpression()) { return; } for (const elem of quasis) { if (elem.value.cooked) { nodes.push(t.stringLiteral(elem.value.cooked)); } if (index < expressions.length) { const expr = expressions[index++]; if (!t.isStringLiteral(expr, { value: '' })) { nodes.push(expr); } } } // + 号连接符必须保证第一和第二个 node 都是字符串 if (!t.isStringLiteral(nodes[0]) && !t.isStringLiteral(nodes[1])) { nodes.unshift(t.stringLiteral('')); } let root = nodes[0]; for (let i = 1; i < nodes.length; i++) { root = t.binaryExpression('+', root, nodes[i]); } path.replaceWith(root); }, ClassDeclaration(path) { mainClass = path; const superClass = utils_1.getSuperClassCode(path); if (superClass) { try { componentProperies = transform({ isRoot: false, isApp: false, code: superClass.code, isTyped: true, sourcePath: superClass.sourcePath, outputPath: superClass.sourcePath }).componentProperies; } catch (error) { // } } }, ClassExpression(path) { mainClass = path; }, ClassMethod(path) { if (t.isIdentifier(path.node.key) && path.node.key.name === 'render') { renderMethod = path; } }, IfStatement(path) { const consequent = path.get('consequent'); if (!consequent.isBlockStatement()) { consequent.replaceWith(t.blockStatement([ consequent.node ])); } }, CallExpression(path) { const callee = path.get('callee'); if (utils_1.isContainJSXElement(path)) { return; } if (callee.isReferencedMemberExpression()) { const id = utils_1.findFirstIdentifierFromMemberExpression(callee.node); const property = callee.node.property; if (t.isIdentifier(property) && property.name.startsWith('on')) { const funcExpr = path.findParent(p => p.isFunctionExpression()); if (funcExpr && funcExpr.isFunctionExpression()) { const taroAPI = funcExpr.findParent(p => p.isCallExpression() && t.isMemberExpression(p.node.callee) && t.isIdentifier(p.node.callee.object, { name: 'Taro' })); if (taroAPI && taroAPI.isCallExpression()) { throw utils_1.codeFrameError(funcExpr.node, '在回调函数使用从 props 传递的函数时,请把回调函数改造为箭头函数并一直使用 `this` 取值'); } } } const calleeIds = getIdsFromMemberProps(callee.node); if (t.isIdentifier(id) && id.name.startsWith('on') && "alipay" /* alipay */ !== adapter_1.Adapter.type) { const fullPath = buildFullPathThisPropsRef(id, calleeIds, path); if (fullPath) { path.replaceWith(t.callExpression(fullPath, path.node.arguments)); } } } if (callee.isReferencedIdentifier()) { const id = callee.node; const ids = [id.name]; if (t.isIdentifier(id) && id.name.startsWith('on')) { const funcExpr = path.findParent(p => p.isFunctionExpression()); if (funcExpr && funcExpr.isFunctionExpression()) { const taroAPI = funcExpr.findParent(p => p.isCallExpression() && t.isMemberExpression(p.node.callee) && t.isIdentifier(p.node.callee.object, { name: 'Taro' })); if (taroAPI && taroAPI.isCallExpression()) { throw utils_1.codeFrameError(funcExpr.node, '在回调函数使用从 props 传递的函数时,请把回调函数改造为箭头函数并一直使用 `this` 取值'); } } const fullPath = buildFullPathThisPropsRef(id, ids, path); if (fullPath) { path.replaceWith(t.callExpression(fullPath, path.node.arguments)); } } } }, // JSXIdentifier (path) { // const parentPath = path.parentPath // if (!parentPath.isJSXAttribute()) { // return // } // const element = parentPath.parentPath // if (!element.isJSXOpeningElement()) { // return // } // const elementName = element.get('name') // if (!elementName.isJSXIdentifier()) { // return // } // if (DEFAULT_Component_SET.has(elementName.node.name)) { // return // } // const expr = parentPath.get('value.expression') // }, JSXElement(path) { const assignment = path.findParent(p => p.isAssignmentExpression()); if (assignment && assignment.isAssignmentExpression() && !options.isTyped) { const left = assignment.node.left; if (t.isIdentifier(left)) { const binding = assignment.scope.getBinding(left.name); if (binding && binding.scope === assignment.scope) { if (binding.path.isVariableDeclarator()) { binding.path.node.init = path.node; assignment.remove(); } else { throw utils_1.codeFrameError(path.node, '同一个作用域的JSX 变量延时赋值没有意义。详见:https://github.com/NervJS/taro/issues/550'); } } } } const switchStatement = path.findParent(p => p.isSwitchStatement()); if (switchStatement && switchStatement.isSwitchStatement()) { const { discriminant, cases } = switchStatement.node; const ifStatement = cases.map((Case, index) => { const [consequent] = Case.consequent; if (!t.isBlockStatement(consequent)) { throw utils_1.codeFrameError(switchStatement.node, '含有 JSX 的 switch case 语句必须每种情况都用花括号 `{}` 包裹结果'); } const block = t.blockStatement(consequent.body.filter(b => !t.isBreakStatement(b))); if (index !== cases.length - 1 && t.isNullLiteral(Case.test)) { throw utils_1.codeFrameError(Case, '含有 JSX 的 switch case 语句只有最后一个 case 才能是 default'); } const test = Case.test === null ? t.nullLiteral() : t.binaryExpression('===', discriminant, Case.test); return { block, test }; }).reduceRight((ifStatement, item) => { if (t.isNullLiteral(item.test)) { ifStatement.alternate = item.block; return ifStatement; } const newStatement = t.ifStatement(item.test, item.block, t.isBooleanLiteral(ifStatement.test, { value: false }) ? ifStatement.alternate : ifStatement); return newStatement; }, t.ifStatement(t.booleanLiteral(false), t.blockStatement([]))); switchStatement.insertAfter(ifStatement); switchStatement.remove(); } const isForStatement = (p) => p && (p.isForStatement() || p.isForInStatement() || p.isForOfStatement()); const forStatement = path.findParent(isForStatement); if (isForStatement(forStatement)) { throw utils_1.codeFrameError(forStatement.node, '不行使用 for 循环操作 JSX 元素,详情:https://github.com/NervJS/taro/blob/master/packages/eslint-plugin-taro/docs/manipulate-jsx-as-array.md'); } const loopCallExpr = path.findParent(p => utils_1.isArrayMapCallExpression(p)); if (loopCallExpr && loopCallExpr.isCallExpression()) { const [func] = loopCallExpr.node.arguments; if (t.isArrowFunctionExpression(func) && !t.isBlockStatement(func.body)) { func.body = t.blockStatement([ t.returnStatement(func.body) ]); } } }, JSXOpeningElement(path) { const { name } = path.node.name; const binding = path.scope.getBinding(name); if (process.env.NODE_ENV !== 'test' && constant_1.DEFAULT_Component_SET.has(name) && binding && binding.kind === 'module') { const bindingPath = binding.path; if (bindingPath.parentPath.isImportDeclaration()) { const source = bindingPath.parentPath.node.source; if (source.value !== constant_1.COMPONENTS_PACKAGE_NAME) { throw utils_1.codeFrameError(bindingPath.parentPath.node, `内置组件名: '${name}' 只能从 ${constant_1.COMPONENTS_PACKAGE_NAME} 引入。`); } } } if (name === 'Provider') { const modules = path.scope.getAllBindings('module'); const providerBinding = Object.values(modules).some((m) => m.identifier.name === 'Provider'); if (providerBinding) { path.node.name = t.jSXIdentifier('view'); const store = path.node.attributes.find(attr => attr.name.name === 'store'); if (store && t.isJSXExpressionContainer(store.value) && t.isIdentifier(store.value.expression)) { storeName = store.value.expression.name; } path.node.attributes = []; } } if (constant_1.IMAGE_COMPONENTS.has(name)) { for (const attr of path.node.attributes) { if (attr.name.name === 'src') { if (t.isStringLiteral(attr.value)) { imageSource.add(attr.value.value); } else if (t.isJSXExpressionContainer(attr.value)) { if (t.isStringLiteral(attr.value.expression)) { imageSource.add(attr.value.expression.value); } } } } } }, JSXAttribute(path) { const { name, value } = path.node; if (options.jsxAttributeNameReplace) { for (const r in options.jsxAttributeNameReplace) { if (options.jsxAttributeNameReplace.hasOwnProperty(r)) { const element = options.jsxAttributeNameReplace[r]; if (t.isJSXIdentifier(name, { name: r })) { path.node.name = t.jSXIdentifier(element); } } } } if (!t.isJSXIdentifier(name) || value === null || t.isStringLiteral(value) || t.isJSXElement(value)) { return; } const expr = value.expression; const exprPath = path.get('value.expression'); const classDecl = path.findParent(p => p.isClassDeclaration()); const classDeclName = classDecl && classDecl.isClassDeclaration() && lodash_1.get(classDecl, 'node.id.name', ''); let isConverted = false; if (classDeclName) { isConverted = classDeclName === '_C' || classDeclName.endsWith('Tmpl'); } if (!t.isBinaryExpression(expr, { operator: '+' }) && !t.isLiteral(expr) && name.name === 'style' && !isConverted) { const jsxID = path.findParent(p => p.isJSXOpeningElement()).get('name'); if (jsxID && jsxID.isJSXIdentifier() && constant_1.DEFAULT_Component_SET.has(jsxID.node.name)) { exprPath.replaceWith(t.callExpression(t.identifier(constant_1.INTERNAL_INLINE_STYLE), [expr])); } } if (name.name.startsWith('on')) { if (exprPath.isReferencedIdentifier()) { const ids = [expr.name]; const fullPath = buildFullPathThisPropsRef(expr, ids, path); if (fullPath) { exprPath.replaceWith(fullPath); } } if (exprPath.isReferencedMemberExpression()) { const id = utils_1.findFirstIdentifierFromMemberExpression(expr); const ids = getIdsFromMemberProps(expr); if (t.isIdentifier(id)) { const fullPath = buildFullPathThisPropsRef(id, ids, path); if (fullPath) { exprPath.replaceWith(fullPath); } } } // @TODO: bind 的处理待定 } }, ImportDeclaration(path) { const source = path.node.source.value; if (importSources.has(source)) { throw utils_1.codeFrameError(path.node, '无法在同一文件重复 import 相同的包。'); } else { importSources.add(source); } const names = []; if (source === constant_1.TARO_PACKAGE_NAME) { isImportTaro = true; path.node.specifiers.push(t.importSpecifier(t.identifier(constant_1.INTERNAL_SAFE_GET), t.identifier(constant_1.INTERNAL_SAFE_GET)), t.importSpecifier(t.identifier(constant_1.INTERNAL_GET_ORIGNAL), t.identifier(constant_1.INTERNAL_GET_ORIGNAL)), t.importSpecifier(t.identifier(constant_1.INTERNAL_INLINE_STYLE), t.identifier(constant_1.INTERNAL_INLINE_STYLE)), t.importSpecifier(t.identifier(constant_1.GEL_ELEMENT_BY_ID), t.identifier(constant_1.GEL_ELEMENT_BY_ID))); } if (source === constant_1.REDUX_PACKAGE_NAME || source === constant_1.MOBX_PACKAGE_NAME) { path.node.specifiers.forEach((s, index, specs) => { if (s.local.name === 'Provider') { specs.splice(index, 1); specs.push(t.importSpecifier(t.identifier('setStore'), t.identifier('setStore'))); } }); } path.traverse({ ImportDefaultSpecifier(path) { const name = path.node.local.name; names.push(name); }, ImportSpecifier(path) { const name = path.node.imported.name; names.push(name); if (source === constant_1.TARO_PACKAGE_NAME && name === 'Component') { path.node.local = t.identifier('__BaseComponent'); } } }); componentSourceMap.set(source, names); } }); if (!isImportTaro) { ast.program.body.unshift(t.importDeclaration([ t.importDefaultSpecifier(t.identifier('Taro')), t.importSpecifier(t.identifier(constant_1.INTERNAL_SAFE_GET), t.identifier(constant_1.INTERNAL_SAFE_GET)), t.importSpecifier(t.identifier(constant_1.INTERNAL_GET_ORIGNAL), t.identifier(constant_1.INTERNAL_GET_ORIGNAL)), t.importSpecifier(t.identifier(constant_1.INTERNAL_INLINE_STYLE), t.identifier(constant_1.INTERNAL_INLINE_STYLE)) ], t.stringLiteral('@tarojs/taro'))); } if (!mainClass) { throw new Error('未找到 Taro.Component 的类定义'); } mainClass.node.body.body.forEach(handleThirdPartyComponent); const storeBinding = mainClass.scope.getBinding(storeName); mainClass.scope.rename('Component', '__BaseComponent'); if (storeBinding) { const statementPath = storeBinding.path.getStatementParent(); if (statementPath) { ast.program.body.forEach((node, index, body) => { if (node === statementPath.node) { body.splice(index + 1, 0, t.expressionStatement(t.callExpression(t.identifier('setStore'), [ t.identifier(storeName) ]))); } }); } } resetTSClassProperty(mainClass.node.body.body); if (options.isApp) { renderMethod.replaceWith(t.classMethod('method', t.identifier('_createData'), [], t.blockStatement([]))); return { ast }; } result = new class_1.Transformer(mainClass, options.sourcePath, componentProperies).result; result.code = babel_generator_1.default(ast).code; result.ast = ast; const lessThanSignReg = new RegExp(constant_1.lessThanSignPlacehold, 'g'); result.compressedTemplate = result.template; result.template = html_1.prettyPrint(result.template, { max_char: 0, unformatted: process.env.NODE_ENV === 'test' ? [] : ['text'] }); result.template = result.template.replace(lessThanSignReg, '<'); result.imageSrcs = Array.from(imageSource); return result; }
); // deopt shadowed functions as transforms like regenerator may try touch the allocation loop state.deopted = state.deopted || !!node.shadow; let start = t.numericLiteral(node.params.length); let key = scope.generateUidIdentifier("key"); let len = scope.generateUidIdentifier("len"); let arrKey = key; let arrLen = len; if (node.params.length) { // this method has additional params, so we need to subtract // the index of the current argument position from the // position in the array that we want to populate arrKey = t.binaryExpression("-", key, start); // we need to work out the size of the array that we're // going to store all the rest parameters // // we need to add a check to avoid constructing the array // with <0 if there are less arguments than params as it'll // cause an error arrLen = t.conditionalExpression( t.binaryExpression(">", len, start), t.binaryExpression("-", len, start), t.numericLiteral(0) ); } let loop = buildRest({
let op = fragment.reduce(function(acc, val) { return t.binaryExpression('+', acc, val); });
Ep.explodeExpression = function(path, ignoreResult) { let expr = path.node; if (expr) { t.assertExpression(expr); } else { return expr; } let self = this; let result; // Used optionally by several cases below. let after; function finish(expr) { t.assertExpression(expr); if (ignoreResult) { self.emit(expr); } else { return expr; } } // If the expression does not contain a leap, then we either emit the // expression as a standalone statement or return it whole. if (!meta.containsLeap(expr)) { return finish(expr); } // If any child contains a leap (such as a yield or labeled continue or // break statement), then any sibling subexpressions will almost // certainly have to be exploded in order to maintain the order of their // side effects relative to the leaping child(ren). let hasLeapingChildren = meta.containsLeap.onlyChildren(expr); // In order to save the rest of explodeExpression from a combinatorial // trainwreck of special cases, explodeViaTempVar is responsible for // deciding when a subexpression needs to be "exploded," which is my // very technical term for emitting the subexpression as an assignment // to a temporary variable and the substituting the temporary variable // for the original subexpression. Think of exploded view diagrams, not // Michael Bay movies. The point of exploding subexpressions is to // control the precise order in which the generated code realizes the // side effects of those subexpressions. function explodeViaTempVar(tempVar, childPath, ignoreChildResult) { assert.ok( !ignoreChildResult || !tempVar, "Ignoring the result of a child expression but forcing it to " + "be assigned to a temporary variable?" ); let result = self.explodeExpression(childPath, ignoreChildResult); if (ignoreChildResult) { // Side effects already emitted above. } else if (tempVar || (hasLeapingChildren && !t.isLiteral(result))) { // If tempVar was provided, then the result will always be assigned // to it, even if the result does not otherwise need to be assigned // to a temporary variable. When no tempVar is provided, we have // the flexibility to decide whether a temporary variable is really // necessary. Unfortunately, in general, a temporary variable is // required whenever any child contains a yield expression, since it // is difficult to prove (at all, let alone efficiently) whether // this result would evaluate to the same value before and after the // yield (see #206). One narrow case where we can prove it doesn't // matter (and thus we do not need a temporary variable) is when the // result in question is a Literal value. result = self.emitAssign( tempVar || self.makeTempVar(), result ); } return result; } // If ignoreResult is true, then we must take full responsibility for // emitting the expression with all its side effects, and we should not // return a result. switch (expr.type) { case "MemberExpression": return finish(t.memberExpression( self.explodeExpression(path.get("object")), expr.computed ? explodeViaTempVar(null, path.get("property")) : expr.property, expr.computed )); case "CallExpression": let calleePath = path.get("callee"); let argsPath = path.get("arguments"); let newCallee; let newArgs = []; let hasLeapingArgs = false; argsPath.forEach(function(argPath) { hasLeapingArgs = hasLeapingArgs || meta.containsLeap(argPath.node); }); if (t.isMemberExpression(calleePath.node)) { if (hasLeapingArgs) { // If the arguments of the CallExpression contained any yield // expressions, then we need to be sure to evaluate the callee // before evaluating the arguments, but if the callee was a member // expression, then we must be careful that the object of the // member expression still gets bound to `this` for the call. let newObject = explodeViaTempVar( // Assign the exploded callee.object expression to a temporary // variable so that we can use it twice without reevaluating it. self.makeTempVar(), calleePath.get("object") ); let newProperty = calleePath.node.computed ? explodeViaTempVar(null, calleePath.get("property")) : calleePath.node.property; newArgs.unshift(newObject); newCallee = t.memberExpression( t.memberExpression( newObject, newProperty, calleePath.node.computed ), t.identifier("call"), false ); } else { newCallee = self.explodeExpression(calleePath); } } else { newCallee = self.explodeExpression(calleePath); if (t.isMemberExpression(newCallee)) { // If the callee was not previously a MemberExpression, then the // CallExpression was "unqualified," meaning its `this` object // should be the global object. If the exploded expression has // become a MemberExpression (e.g. a context property, probably a // temporary variable), then we need to force it to be unqualified // by using the (0, object.property)(...) trick; otherwise, it // will receive the object of the MemberExpression as its `this` // object. newCallee = t.sequenceExpression([ t.numbericLiteral(0), newCallee ]); } } argsPath.forEach(function(argPath) { newArgs.push(explodeViaTempVar(null, argPath)); }); return finish(t.callExpression( newCallee, newArgs )); case "NewExpression": return finish(t.newExpression( explodeViaTempVar(null, path.get("callee")), path.get("arguments").map(function(argPath) { return explodeViaTempVar(null, argPath); }) )); case "ObjectExpression": return finish(t.objectExpression( path.get("properties").map(function(propPath) { if (propPath.isObjectProperty()) { return t.objectProperty( propPath.node.key, explodeViaTempVar(null, propPath.get("value")), propPath.node.computed ); } else { return propPath.node; } }) )); case "ArrayExpression": return finish(t.arrayExpression( path.get("elements").map(function(elemPath) { return explodeViaTempVar(null, elemPath); }) )); case "SequenceExpression": let lastIndex = expr.expressions.length - 1; path.get("expressions").forEach(function(exprPath) { if (exprPath.key === lastIndex) { result = self.explodeExpression(exprPath, ignoreResult); } else { self.explodeExpression(exprPath, true); } }); return result; case "LogicalExpression": after = loc(); if (!ignoreResult) { result = self.makeTempVar(); } let left = explodeViaTempVar(result, path.get("left")); if (expr.operator === "&&") { self.jumpIfNot(left, after); } else { assert.strictEqual(expr.operator, "||"); self.jumpIf(left, after); } explodeViaTempVar(result, path.get("right"), ignoreResult); self.mark(after); return result; case "ConditionalExpression": let elseLoc = loc(); after = loc(); let test = self.explodeExpression(path.get("test")); self.jumpIfNot(test, elseLoc); if (!ignoreResult) { result = self.makeTempVar(); } explodeViaTempVar(result, path.get("consequent"), ignoreResult); self.jump(after); self.mark(elseLoc); explodeViaTempVar(result, path.get("alternate"), ignoreResult); self.mark(after); return result; case "UnaryExpression": return finish(t.unaryExpression( expr.operator, // Can't (and don't need to) break up the syntax of the argument. // Think about delete a[b]. self.explodeExpression(path.get("argument")), !!expr.prefix )); case "BinaryExpression": return finish(t.binaryExpression( expr.operator, explodeViaTempVar(null, path.get("left")), explodeViaTempVar(null, path.get("right")) )); case "AssignmentExpression": return finish(t.assignmentExpression( expr.operator, self.explodeExpression(path.get("left")), self.explodeExpression(path.get("right")) )); case "UpdateExpression": return finish(t.updateExpression( expr.operator, self.explodeExpression(path.get("argument")), expr.prefix )); case "YieldExpression": after = loc(); let arg = expr.argument && self.explodeExpression(path.get("argument")); if (arg && expr.delegate) { let result = self.makeTempVar(); self.emit(t.returnStatement(t.callExpression( self.contextProperty("delegateYield"), [ arg, t.stringLiteral(result.property.name), after ] ))); self.mark(after); return result; } self.emitAssign(self.contextProperty("next"), after); self.emit(t.returnStatement(arg || null)); self.mark(after); return self.contextProperty("sent"); default: throw new Error( "unknown Expression of type " + JSON.stringify(expr.type)); } };
export default function () { let REASSIGN_REMAP_SKIP = Symbol(); let reassignmentVisitor = { ReferencedIdentifier(path) { let name = path.node.name; let remap = this.remaps[name]; if (!remap) return; // redeclared in this scope if (this.scope.getBinding(name) !== path.scope.getBinding(name)) return; if (path.parentPath.isCallExpression({ callee: path.node })) { path.replaceWith(t.sequenceExpression([t.numericLiteral(0), remap])); } else { path.replaceWith(remap); } this.requeueInParent(path); }, AssignmentExpression(path) { let node = path.node; if (node[REASSIGN_REMAP_SKIP]) return; let left = path.get("left"); if (!left.isIdentifier()) return; let name = left.node.name; let exports = this.exports[name]; if (!exports) return; // redeclared in this scope if (this.scope.getBinding(name) !== path.scope.getBinding(name)) return; node[REASSIGN_REMAP_SKIP] = true; for (let reid of exports) { node = buildExportsAssignment(reid, node).expression; } path.replaceWith(node); this.requeueInParent(path); }, UpdateExpression(path) { let arg = path.get("argument"); if (!arg.isIdentifier()) return; let name = arg.node.name; let exports = this.exports[name]; if (!exports) return; // redeclared in this scope if (this.scope.getBinding(name) !== path.scope.getBinding(name)) return; let node = t.assignmentExpression(path.node.operator[0] + "=", arg.node, t.numericLiteral(1)); if ((path.parentPath.isExpressionStatement() && !path.isCompletionRecord()) || path.node.prefix) { path.replaceWith(node); this.requeueInParent(path); return; } let nodes = []; nodes.push(node); let operator; if (path.node.operator === "--") { operator = "+"; } else { // "++" operator = "-"; } nodes.push(t.binaryExpression(operator, arg.node, t.numericLiteral(1))); let newPaths = path.replaceWithMultiple(t.sequenceExpression(nodes)); for (const newPath of newPaths) this.requeueInParent(newPath); } }; return { inherits: require("babel-plugin-transform-strict-mode"), visitor: { ThisExpression(path, state) { // If other plugins run after this plugin's Program#exit handler, we allow them to // insert top-level `this` values. This allows the AMD and UMD plugins to // function properly. if (this.ranCommonJS) return; if ( state.opts.allowTopLevelThis !== true && !path.findParent((path) => !path.is("shadow") && THIS_BREAK_KEYS.indexOf(path.type) >= 0) ) { path.replaceWith(t.identifier("undefined")); } }, Program: { exit(path) { this.ranCommonJS = true; let strict = !!this.opts.strict; let { scope } = path; // rename these commonjs variables if they're declared in the file scope.rename("module"); scope.rename("exports"); scope.rename("require"); let hasExports = false; let hasImports = false; let body: Array<Object> = path.get("body"); let imports = Object.create(null); let exports = Object.create(null); let nonHoistedExportNames = Object.create(null); let topNodes = []; let remaps = Object.create(null); let requires = Object.create(null); function addRequire(source, blockHoist) { let cached = requires[source]; if (cached) return cached; let ref = path.scope.generateUidIdentifier(basename(source, extname(source))); let varDecl = t.variableDeclaration("var", [ t.variableDeclarator(ref, buildRequire( t.stringLiteral(source) ).expression) ]); if (typeof blockHoist === "number" && blockHoist > 0) { varDecl._blockHoist = blockHoist; } topNodes.push(varDecl); return requires[source] = ref; } function addTo(obj, key, arr) { let existing = obj[key] || []; obj[key] = existing.concat(arr); } for (let path of body) { if (path.isExportDeclaration()) { hasExports = true; let specifiers = [].concat(path.get("declaration"), path.get("specifiers")); for (let specifier of specifiers) { let ids = specifier.getBindingIdentifiers(); if (ids.__esModule) { throw specifier.buildCodeFrameError("Illegal export \"__esModule\""); } } } if (path.isImportDeclaration()) { hasImports = true; let key = path.node.source.value; let importsEntry = imports[key] || { specifiers: [], maxBlockHoist: 0 }; importsEntry.specifiers.push(...path.node.specifiers); if (typeof path.node._blockHoist === "number") { importsEntry.maxBlockHoist = Math.max( path.node._blockHoist, importsEntry.maxBlockHoist ); } imports[key] = importsEntry; path.remove(); } else if (path.isExportDefaultDeclaration()) { let declaration = path.get("declaration"); if (declaration.isFunctionDeclaration()) { let id = declaration.node.id; let defNode = t.identifier("default"); if (id) { addTo(exports, id.name, defNode); topNodes.push(buildExportsAssignment(defNode, id)); path.replaceWith(declaration.node); } else { topNodes.push(buildExportsAssignment(defNode, t.toExpression(declaration.node))); path.remove(); } } else if (declaration.isClassDeclaration()) { let id = declaration.node.id; let defNode = t.identifier("default"); if (id) { addTo(exports, id.name, defNode); path.replaceWithMultiple([ declaration.node, buildExportsAssignment(defNode, id) ]); } else { path.replaceWith(buildExportsAssignment(defNode, t.toExpression(declaration.node))); } } else { path.replaceWith(buildExportsAssignment(t.identifier("default"), declaration.node)); } } else if (path.isExportNamedDeclaration()) { let declaration = path.get("declaration"); if (declaration.node) { if (declaration.isFunctionDeclaration()) { let id = declaration.node.id; addTo(exports, id.name, id); topNodes.push(buildExportsAssignment(id, id)); path.replaceWith(declaration.node); } else if (declaration.isClassDeclaration()) { let id = declaration.node.id; addTo(exports, id.name, id); path.replaceWithMultiple([ declaration.node, buildExportsAssignment(id, id) ]); nonHoistedExportNames[id.name] = true; } else if (declaration.isVariableDeclaration()) { let declarators = declaration.get("declarations"); for (let decl of declarators) { let id = decl.get("id"); let init = decl.get("init"); if (!init.node) init.replaceWith(t.identifier("undefined")); if (id.isIdentifier()) { addTo(exports, id.node.name, id.node); init.replaceWith(buildExportsAssignment(id.node, init.node).expression); nonHoistedExportNames[id.node.name] = true; } else { // todo } } path.replaceWith(declaration.node); } continue; } let specifiers = path.get("specifiers"); if (specifiers.length) { let nodes = []; let source = path.node.source; if (source) { let ref = addRequire(source.value, path.node._blockHoist); for (let specifier of specifiers) { if (specifier.isExportNamespaceSpecifier()) { // todo } else if (specifier.isExportDefaultSpecifier()) { // todo } else if (specifier.isExportSpecifier()) { if (specifier.node.local.name === "default") { topNodes.push(buildExportsFrom(t.stringLiteral(specifier.node.exported.name), t.memberExpression(t.callExpression(this.addHelper("interopRequireDefault"), [ref]), specifier.node.local))); } else { topNodes.push(buildExportsFrom(t.stringLiteral(specifier.node.exported.name), t.memberExpression(ref, specifier.node.local))); } nonHoistedExportNames[specifier.node.exported.name] = true; } } } else { for (let specifier of specifiers) { if (specifier.isExportSpecifier()) { addTo(exports, specifier.node.local.name, specifier.node.exported); nonHoistedExportNames[specifier.node.exported.name] = true; nodes.push(buildExportsAssignment(specifier.node.exported, specifier.node.local)); } } } path.replaceWithMultiple(nodes); } } else if (path.isExportAllDeclaration()) { topNodes.push(buildExportAll({ KEY: path.scope.generateUidIdentifier("key"), OBJECT: addRequire(path.node.source.value, path.node._blockHoist) })); path.remove(); } } for (let source in imports) { let {specifiers, maxBlockHoist} = imports[source]; if (specifiers.length) { let uid = addRequire(source, maxBlockHoist); let wildcard; for (let i = 0; i < specifiers.length; i++) { let specifier = specifiers[i]; if (t.isImportNamespaceSpecifier(specifier)) { if (strict) { remaps[specifier.local.name] = uid; } else { const varDecl = t.variableDeclaration("var", [ t.variableDeclarator( specifier.local, t.callExpression( this.addHelper("interopRequireWildcard"), [uid] ) ) ]); if (maxBlockHoist > 0) { varDecl._blockHoist = maxBlockHoist; } topNodes.push(varDecl); } wildcard = specifier.local; } else if (t.isImportDefaultSpecifier(specifier)) { specifiers[i] = t.importSpecifier(specifier.local, t.identifier("default")); } } for (let specifier of specifiers) { if (t.isImportSpecifier(specifier)) { let target = uid; if (specifier.imported.name === "default") { if (wildcard) { target = wildcard; } else { target = wildcard = path.scope.generateUidIdentifier(uid.name); const varDecl = t.variableDeclaration("var", [ t.variableDeclarator( target, t.callExpression( this.addHelper("interopRequireDefault"), [uid] ) ) ]); if (maxBlockHoist > 0) { varDecl._blockHoist = maxBlockHoist; } topNodes.push(varDecl); } } remaps[specifier.local.name] = t.memberExpression(target, t.cloneWithoutLoc(specifier.imported)); } } } else { // bare import topNodes.push(buildRequire(t.stringLiteral(source))); } } if (hasImports && Object.keys(nonHoistedExportNames).length) { let hoistedExportsNode = t.identifier("undefined"); for (let name in nonHoistedExportNames) { hoistedExportsNode = buildExportsAssignment(t.identifier(name), hoistedExportsNode).expression; } const node = t.expressionStatement(hoistedExportsNode); node._blockHoist = 3; topNodes.unshift(node); } // add __esModule declaration if this file has any exports if (hasExports && !strict) { let buildTemplate = buildExportsModuleDeclaration; if (this.opts.loose) buildTemplate = buildLooseExportsModuleDeclaration; const declar = buildTemplate(); declar._blockHoist = 3; topNodes.unshift(declar); } path.unshiftContainer("body", topNodes); path.traverse(reassignmentVisitor, { remaps, scope, exports, requeueInParent: (newPath) => path.requeue(newPath), }); } } } }; }
visitMixin: function(mixin){ var ast = []; var self = this; var name = 'pug_mixins['; var args = mixin.args || ''; var block = mixin.block; var attrs = mixin.attrs; var attrsBlocks = this.attributeBlocks(mixin.attributeBlocks); var pp = this.pp; var dynamic = mixin.name[0]==='#'; var key = mixin.name; if (dynamic) this.dynamicMixins = true; name += (dynamic ? mixin.name.substr(2,mixin.name.length-3):'"'+mixin.name+'"')+']'; var mixinName = dynamic ? (mixin.astName || this.parseExpr(mixin.name.substr(2,mixin.name.length-3))): t.stringLiteral(mixin.name); this.mixins[key] = this.mixins[key] || {used: false, instances: []}; // mixin invocation if (mixin.call) { this.mixins[key].used = true; if (pp) { ast.push(t.expressionStatement(t.callExpression( t.memberExpression(t.identifier('pug_indent'), t.identifier('push')), [t.stringLiteral(Array(this.indents + 1).join(pp))] ))) } if (block || attrs.length || attrsBlocks.length) { var astArgs = [] ast.push( t.expressionStatement(this.wrapCallExpression(t.callExpression( t.memberExpression( t.memberExpression(t.identifier('pug_mixins'), mixinName, true), t.identifier("call") ), astArgs ))) ); var astObj, astKey; if (block || attrsBlocks.length || attrs.length) { astKey = []; astObj = t.objectExpression(astKey); astArgs.push(astObj); } if (block) { var astFunc = []; // Render block with no indents, dynamically added when rendered this.parentIndents++; var _indents = this.indents; this.indents = 0; push.apply(astFunc, this.visit(mixin.block, mixin)); this.indents = _indents; this.parentIndents--; astKey.push(t.objectProperty( t.identifier('block'), t.functionExpression( null, [], t.blockStatement(astFunc), this.useGenerators ) )); } if (attrsBlocks.length) { if (attrs.length) { var val = this.attrs(attrs); attrsBlocks.unshift(val); } if (attrsBlocks.length > 1) { astKey.push(t.objectProperty( t.identifier('attributes'), t.callExpression( this.runtime('merge', true), attrsBlocks.map(function(b) { return self.parseExpr(b) }) ) )); } else { astKey.push(t.objectProperty( t.identifier('attributes'), this.parseExpr(attrsBlocks[0]) )); } } else if (attrs.length) { var val = this.attrs(attrs); astKey.push(t.objectProperty( t.identifier('attributes'), this.parseExpr(val) )); } if (args) { args = args ? args.split(',') : []; Array.prototype.push.apply(astArgs, mixin.astArgs || this.parseArgs(args) ) } } else { var astArgs = mixin.astArgs || this.parseArgs(args); ast.push(t.expressionStatement(this.wrapCallExpression(t.callExpression( t.memberExpression(t.identifier('pug_mixins'), mixinName, true), astArgs )))); } if (pp) { ast.push(t.expressionStatement(t.callExpression( t.memberExpression(t.identifier('pug_indent'), t.identifier('pop')), [] ))) } } // mixin definition else { args = args ? args.split(',') : []; var rest; if (args.length && /^\.\.\./.test(args[args.length - 1].trim())) { rest = args.pop().trim().replace(/^\.\.\./, ''); } var astArgs = args.map(function(arg) { return t.identifier(arg.trim())}) // we need use pug_interp here for v8: https://code.google.com/p/v8/issues/detail?id=4165 // once fixed, use this: this.buf.push(name + ' = function(' + args.join(',') + '){'); var astMixin = []; astMixin.push( t.variableDeclaration('var', [ t.variableDeclarator( t.identifier('block'), t.logicalExpression('&&', t.thisExpression(), t.memberExpression(t.thisExpression(), t.identifier('block'))) ), t.variableDeclarator( t.identifier('attributes'), t.logicalExpression('||', t.logicalExpression('&&', t.thisExpression(), t.memberExpression(t.thisExpression(), t.identifier('attributes'))), t.objectExpression([]) ) ) ]) ) if (rest) { astMixin.push( t.variableDeclaration('var', [ t.variableDeclarator( t.identifier(rest), t.arrayExpression([]) ) ]) ) astMixin.push( t.forStatement( t.assignmentExpression('=', t.identifier('pug_interp'), t.numericLiteral(args.length)), t.binaryExpression('<', t.identifier('pug_interp'), t.memberExpression(t.identifier('arguments'), t.identifier('length'))), t.updateExpression('++', t.identifier('pug_interp'), false), t.expressionStatement( t.callExpression( t.memberExpression(t.identifier(rest), t.identifier('push')), [t.memberExpression(t.identifier('arguments'), t.identifier('pug_interp'), true)] ) ) ) ) } this.parentIndents++; push.apply(astMixin, this.visit(block, mixin)); this.parentIndents--; var mixinStmt = t.expressionStatement( t.assignmentExpression( '=', t.memberExpression(t.identifier('pug_mixins'), mixinName, true), t.assignmentExpression( '=', t.identifier('pug_interp'), t.functionExpression( null, astArgs, t.blockStatement(astMixin), this.useGenerators ) ) ) ); ast.push(mixinStmt); this.mixins[key].instances.push({stmt: mixinStmt}); } return ast; },
import * as babylon from 'babylon' import traverse from 'babel-traverse' import generate from 'babel-generator' import * as t from 'babel-types' const code = ` import a from 'b' n === 3 ` const ast = babylon.parse(code, { sourceType: 'module' }) const visitor = { BinaryExpression(path) { console.log((path.node)) path.replaceWith(t.binaryExpression('**', t.numericLiteral(3), t.numericLiteral(4))) } } traverse(ast, visitor) let generated = generate(ast, null, code) console.log(generated.code)
visitEach: function(each){ var ast = []; var indexVarName = each.key || 'pug_index' + this.eachCount; this.eachCount++; var body = [ t.variableDeclaration('var', [ t.variableDeclarator(t.identifier('$$obj'), each.astObj || this.parseExpr(each.obj)) ]) ] var func = t.expressionStatement( this.wrapCallExpression(t.callExpression( t.memberExpression( t.functionExpression( null, [], t.blockStatement(body), this.useGenerators ), t.identifier('call') ) ,[t.thisExpression()] )) ) ast.push(func) var blockEach = [ t.variableDeclaration('var', [ t.variableDeclarator(t.identifier(each.val), t.memberExpression(t.identifier('$$obj'), t.identifier(indexVarName), true) ) ]) ]; var blockAlt = []; push.apply(blockEach, this.visit(each.block, each)); var arrayLoop = t.blockStatement([t.forStatement( t.variableDeclaration('var', [ t.variableDeclarator(t.identifier(indexVarName), t.numericLiteral(0)), t.variableDeclarator(t.identifier('$$l'), t.memberExpression(t.identifier('$$obj'), t.identifier('length'))) ]), t.binaryExpression('<', t.identifier(indexVarName), t.identifier('$$l')), t.updateExpression('++', t.identifier(indexVarName), false), t.blockStatement(blockEach) )]); var blockObj = [ t.expressionStatement(t.updateExpression('++', t.identifier('$$l'), false)), t.variableDeclaration('var', [ t.variableDeclarator(t.identifier(each.val), t.memberExpression(t.identifier('$$obj'), t.identifier(indexVarName), true) ) ]) ] var blockObjAlt = []; push.apply(blockObj, this.visit(each.block, each)); var objectLoop = t.blockStatement([ t.variableDeclaration('var', [ t.variableDeclarator(t.identifier('$$l'), t.numericLiteral(0)) ]), t.forInStatement( t.variableDeclaration('var', [ t.variableDeclarator(t.identifier(indexVarName)) ]), t.identifier('$$obj'), t.blockStatement(blockObj) ) ]) if (each.alternate) { push.apply(blockAlt, this.visit(each.alternate, each)); arrayLoop = t.ifStatement( t.memberExpression(t.identifier('$$obj'), t.identifier('length')), arrayLoop, t.blockStatement(blockAlt) ); } if (each.alternate) { push.apply(blockObjAlt, this.visit(each.alternate, each)); objectLoop.body.push(t.ifStatement( t.binaryExpression('===', t.identifier('$$l'), t.numericLiteral(0)), t.blockStatement(blockObjAlt) )) } var it = t.ifStatement( t.binaryExpression('==', t.stringLiteral('number'), t.unaryExpression('typeof', t.memberExpression(t.identifier('$$obj'), t.identifier('length')))), arrayLoop, objectLoop) body.push(it); return ast; },
const classNamePropValueForReals = classNameObjects.reduce((acc, val) => { if (!acc) { if ( // pass conditional expressions through t.isConditionalExpression(val) || // pass non-null literals through (t.isLiteral(val) && val.value !== null) ) { return val; } return t.logicalExpression('||', val, t.stringLiteral('')); } const accIsString = t.isLiteral(acc) && typeof acc.value === 'string'; let inner; if (t.isLiteral(val)) { if (typeof val.value === 'string') { if (accIsString) { // join adjacent string literals return t.stringLiteral(`${acc.value} ${val.value}`); } inner = t.stringLiteral(` ${val.value}`); } else { inner = t.binaryExpression('+', t.stringLiteral(' '), val); } } else if ( t.isConditionalExpression(val) || t.isBinaryExpression(val) ) { if (accIsString) { return t.binaryExpression( '+', t.stringLiteral(`${acc.value} `), val ); } inner = t.binaryExpression('+', t.stringLiteral(' '), val); } else if (t.isIdentifier(val) || t.isMemberExpression(val)) { // identifiers and member expressions make for reasonable ternaries inner = t.conditionalExpression( val, t.binaryExpression('+', t.stringLiteral(' '), val), t.stringLiteral('') ); } else { if (accIsString) { return t.binaryExpression( '+', t.stringLiteral(`${acc.value} `), t.logicalExpression('||', val, t.stringLiteral('')) ); } // use a logical expression for more complex prop values inner = t.binaryExpression( '+', t.stringLiteral(' '), t.logicalExpression('||', val, t.stringLiteral('')) ); } return t.binaryExpression('+', acc, inner); }, null);
Ep.explodeStatement = function(path, labelId) { let stmt = path.node; let self = this; let before, after, head; t.assertStatement(stmt); if (labelId) { t.assertIdentifier(labelId); } else { labelId = null; } // Explode BlockStatement nodes even if they do not contain a yield, // because we don't want or need the curly braces. if (t.isBlockStatement(stmt)) { path.get("body").forEach(function (path) { self.explodeStatement(path); }); return; } if (!meta.containsLeap(stmt)) { // Technically we should be able to avoid emitting the statement // altogether if !meta.hasSideEffects(stmt), but that leads to // confusing generated code (for instance, `while (true) {}` just // disappears) and is probably a more appropriate job for a dedicated // dead code elimination pass. self.emit(stmt); return; } switch (stmt.type) { case "ExpressionStatement": self.explodeExpression(path.get("expression"), true); break; case "LabeledStatement": after = loc(); // Did you know you can break from any labeled block statement or // control structure? Well, you can! Note: when a labeled loop is // encountered, the leap.LabeledEntry created here will immediately // enclose a leap.LoopEntry on the leap manager's stack, and both // entries will have the same label. Though this works just fine, it // may seem a bit redundant. In theory, we could check here to // determine if stmt knows how to handle its own label; for example, // stmt happens to be a WhileStatement and so we know it's going to // establish its own LoopEntry when we explode it (below). Then this // LabeledEntry would be unnecessary. Alternatively, we might be // tempted not to pass stmt.label down into self.explodeStatement, // because we've handled the label here, but that's a mistake because // labeled loops may contain labeled continue statements, which is not // something we can handle in this generic case. All in all, I think a // little redundancy greatly simplifies the logic of this case, since // it's clear that we handle all possible LabeledStatements correctly // here, regardless of whether they interact with the leap manager // themselves. Also remember that labels and break/continue-to-label // statements are rare, and all of this logic happens at transform // time, so it has no additional runtime cost. self.leapManager.withEntry( new leap.LabeledEntry(after, stmt.label), function() { self.explodeStatement(path.get("body"), stmt.label); } ); self.mark(after); break; case "WhileStatement": before = loc(); after = loc(); self.mark(before); self.jumpIfNot(self.explodeExpression(path.get("test")), after); self.leapManager.withEntry( new leap.LoopEntry(after, before, labelId), function() { self.explodeStatement(path.get("body")); } ); self.jump(before); self.mark(after); break; case "DoWhileStatement": let first = loc(); let test = loc(); after = loc(); self.mark(first); self.leapManager.withEntry( new leap.LoopEntry(after, test, labelId), function() { self.explode(path.get("body")); } ); self.mark(test); self.jumpIf(self.explodeExpression(path.get("test")), first); self.mark(after); break; case "ForStatement": head = loc(); let update = loc(); after = loc(); if (stmt.init) { // We pass true here to indicate that if stmt.init is an expression // then we do not care about its result. self.explode(path.get("init"), true); } self.mark(head); if (stmt.test) { self.jumpIfNot(self.explodeExpression(path.get("test")), after); } else { // No test means continue unconditionally. } self.leapManager.withEntry( new leap.LoopEntry(after, update, labelId), function() { self.explodeStatement(path.get("body")); } ); self.mark(update); if (stmt.update) { // We pass true here to indicate that if stmt.update is an // expression then we do not care about its result. self.explode(path.get("update"), true); } self.jump(head); self.mark(after); break; case "TypeCastExpression": return self.explodeExpression(path.get("expression")); case "ForInStatement": head = loc(); after = loc(); let keyIterNextFn = self.makeTempVar(); self.emitAssign( keyIterNextFn, t.callExpression( util.runtimeProperty("keys"), [self.explodeExpression(path.get("right"))] ) ); self.mark(head); let keyInfoTmpVar = self.makeTempVar(); self.jumpIf( t.memberExpression( t.assignmentExpression( "=", keyInfoTmpVar, t.callExpression(keyIterNextFn, []) ), t.identifier("done"), false ), after ); self.emitAssign( stmt.left, t.memberExpression( keyInfoTmpVar, t.identifier("value"), false ) ); self.leapManager.withEntry( new leap.LoopEntry(after, head, labelId), function() { self.explodeStatement(path.get("body")); } ); self.jump(head); self.mark(after); break; case "BreakStatement": self.emitAbruptCompletion({ type: "break", target: self.leapManager.getBreakLoc(stmt.label) }); break; case "ContinueStatement": self.emitAbruptCompletion({ type: "continue", target: self.leapManager.getContinueLoc(stmt.label) }); break; case "SwitchStatement": // Always save the discriminant into a temporary variable in case the // test expressions overwrite values like context.sent. let disc = self.emitAssign( self.makeTempVar(), self.explodeExpression(path.get("discriminant")) ); after = loc(); let defaultLoc = loc(); let condition = defaultLoc; let caseLocs = []; // If there are no cases, .cases might be undefined. let cases = stmt.cases || []; for (let i = cases.length - 1; i >= 0; --i) { let c = cases[i]; t.assertSwitchCase(c); if (c.test) { condition = t.conditionalExpression( t.binaryExpression("===", disc, c.test), caseLocs[i] = loc(), condition ); } else { caseLocs[i] = defaultLoc; } } let discriminant = path.get("discriminant"); discriminant.replaceWith(condition); self.jump(self.explodeExpression(discriminant)); self.leapManager.withEntry( new leap.SwitchEntry(after), function() { path.get("cases").forEach(function(casePath) { let i = casePath.key; self.mark(caseLocs[i]); casePath.get("consequent").forEach(function (path) { self.explodeStatement(path); }); }); } ); self.mark(after); if (defaultLoc.value === -1) { self.mark(defaultLoc); assert.strictEqual(after.value, defaultLoc.value); } break; case "IfStatement": let elseLoc = stmt.alternate && loc(); after = loc(); self.jumpIfNot( self.explodeExpression(path.get("test")), elseLoc || after ); self.explodeStatement(path.get("consequent")); if (elseLoc) { self.jump(after); self.mark(elseLoc); self.explodeStatement(path.get("alternate")); } self.mark(after); break; case "ReturnStatement": self.emitAbruptCompletion({ type: "return", value: self.explodeExpression(path.get("argument")) }); break; case "WithStatement": throw new Error("WithStatement not supported in generator functions."); case "TryStatement": after = loc(); let handler = stmt.handler; let catchLoc = handler && loc(); let catchEntry = catchLoc && new leap.CatchEntry( catchLoc, handler.param ); let finallyLoc = stmt.finalizer && loc(); let finallyEntry = finallyLoc && new leap.FinallyEntry(finallyLoc, after); let tryEntry = new leap.TryEntry( self.getUnmarkedCurrentLoc(), catchEntry, finallyEntry ); self.tryEntries.push(tryEntry); self.updateContextPrevLoc(tryEntry.firstLoc); self.leapManager.withEntry(tryEntry, function() { self.explodeStatement(path.get("block")); if (catchLoc) { if (finallyLoc) { // If we have both a catch block and a finally block, then // because we emit the catch block first, we need to jump over // it to the finally block. self.jump(finallyLoc); } else { // If there is no finally block, then we need to jump over the // catch block to the fall-through location. self.jump(after); } self.updateContextPrevLoc(self.mark(catchLoc)); let bodyPath = path.get("handler.body"); let safeParam = self.makeTempVar(); self.clearPendingException(tryEntry.firstLoc, safeParam); bodyPath.traverse(catchParamVisitor, { safeParam: safeParam, catchParamName: handler.param.name }); self.leapManager.withEntry(catchEntry, function() { self.explodeStatement(bodyPath); }); } if (finallyLoc) { self.updateContextPrevLoc(self.mark(finallyLoc)); self.leapManager.withEntry(finallyEntry, function() { self.explodeStatement(path.get("finalizer")); }); self.emit(t.returnStatement(t.callExpression( self.contextProperty("finish"), [finallyEntry.firstLoc] ))); } }); self.mark(after); break; case "ThrowStatement": self.emit(t.throwStatement( self.explodeExpression(path.get("argument")) )); break; default: throw new Error( "unknown Statement of type " + JSON.stringify(stmt.type)); } };