Esempio n. 1
0
 function toConstant(expression: b.Expression): any {
   if (!constant) return;
   if (b.isArrayExpression(expression)) {
     const result = [];
     for (let i = 0; constant && i < expression.elements.length; i++) {
       const element = expression.elements[i];
       if (b.isSpreadElement(element)) {
         const spread = toConstant(element.argument);
         if (!(isSpreadable(spread) && constant)) {
           constant = false;
         } else {
           result.push(...spread);
         }
       } else {
         result.push(toConstant(element));
       }
     }
     return result;
   }
   if (b.isBinaryExpression(expression)) {
     const left = toConstant(expression.left);
     const right = toConstant(expression.right);
     return constant && binaryOperation(expression.operator, left, right);
   }
   if (b.isBooleanLiteral(expression)) {
     return expression.value;
   }
   if (b.isCallExpression(expression)) {
     const args = [];
     for (let i = 0; constant && i < expression.arguments.length; i++) {
       const arg = expression.arguments[i];
       if (b.isSpreadElement(arg)) {
         const spread = toConstant(arg.argument);
         if (!(isSpreadable(spread) && constant)) {
           constant = false;
         } else {
           args.push(...spread);
         }
       } else {
         args.push(toConstant(arg));
       }
     }
     if (!constant) return;
     if (b.isMemberExpression(expression.callee)) {
       const object = toConstant(expression.callee.object);
       if (!object || !constant) {
         constant = false;
         return;
       }
       const member = expression.callee.computed
         ? toConstant(expression.callee.property)
         : b.isIdentifier(expression.callee.property)
           ? expression.callee.property.name
           : undefined;
       if (member === undefined && !expression.callee.computed) {
         constant = false;
       }
       if (!constant) return;
       if (canCallMethod(object, '' + member)) {
         return object[member].apply(object, args);
       }
     } else {
       const callee = toConstant(expression.callee);
       if (!constant) return;
       return callee.apply(null, args);
     }
   }
   if (b.isConditionalExpression(expression)) {
     const test = toConstant(expression.test);
     return test
       ? toConstant(expression.consequent)
       : toConstant(expression.alternate);
   }
   if (b.isIdentifier(expression)) {
     if (
       options.constants &&
       {}.hasOwnProperty.call(options.constants, expression.name)
     ) {
       return options.constants[expression.name];
     }
   }
   if (b.isLogicalExpression(expression)) {
     const left = toConstant(expression.left);
     const right = toConstant(expression.right);
     if (constant && expression.operator === '&&') {
       return left && right;
     }
     if (constant && expression.operator === '||') {
       return left || right;
     }
   }
   if (b.isMemberExpression(expression)) {
     const object = toConstant(expression.object);
     if (!object || !constant) {
       constant = false;
       return;
     }
     const member = expression.computed
       ? toConstant(expression.property)
       : b.isIdentifier(expression.property)
         ? expression.property.name
         : undefined;
     if (member === undefined && !expression.computed) {
       constant = false;
     }
     if (!constant) return;
     if ({}.hasOwnProperty.call(object, '' + member) && member[0] !== '_') {
       return object[member];
     }
   }
   if (b.isNullLiteral(expression)) {
     return null;
   }
   if (b.isNumericLiteral(expression)) {
     return expression.value;
   }
   if (b.isObjectExpression(expression)) {
     const result: any = {};
     for (let i = 0; constant && i < expression.properties.length; i++) {
       const property = expression.properties[i];
       if (b.isObjectProperty(property)) {
         if (property.shorthand) {
           constant = false;
           return;
         }
         const key = property.computed
           ? toConstant(property.key)
           : b.isIdentifier(property.key)
             ? property.key.name
             : b.isStringLiteral(property.key)
               ? property.key.value
               : undefined;
         if (!key || key[0] === '_') {
           constant = false;
         }
         if (!constant) return;
         const value = toConstant(property.value);
         if (!constant) return;
         result[key] = value;
       } else if (b.isObjectMethod(property)) {
         constant = false;
       } else if (b.isSpreadProperty(property)) {
         const argument = toConstant(property.argument);
         if (!argument) constant = false;
         if (!constant) return;
         Object.assign(result, argument);
       }
     }
     return result;
   }
   if (b.isParenthesizedExpression(expression)) {
     return toConstant(expression.expression);
   }
   if (b.isRegExpLiteral(expression)) {
     return new RegExp(expression.pattern, expression.flags);
   }
   if (b.isSequenceExpression(expression)) {
     for (let i = 0; i < expression.expressions.length - 1 && constant; i++) {
       toConstant(expression.expressions[i]);
     }
     return toConstant(
       expression.expressions[expression.expressions.length - 1],
     );
   }
   if (b.isStringLiteral(expression)) {
     return expression.value;
   }
   // TODO: TaggedTemplateExpression
   if (b.isTemplateLiteral(expression)) {
     let result = '';
     for (let i = 0; i < expression.quasis.length; i++) {
       const quasi = expression.quasis[i];
       result += quasi.value.cooked;
       if (i < expression.expressions.length) {
         result += '' + toConstant(expression.expressions[i]);
       }
     }
     return result;
   }
   if (b.isUnaryExpression(expression)) {
     const argument = toConstant(expression.argument);
     if (!constant) {
       return;
     }
     switch (expression.operator) {
       case '-':
         return -argument;
       case '+':
         return +argument;
       case '!':
         return !argument;
       case '~':
         return ~argument;
       case 'typeof':
         return typeof argument;
       case 'void':
         return void argument;
     }
   }
   constant = false;
 }
Esempio n. 2
0
export function parseLoopBody (
  body: NodePath<t.BlockStatement>,
  jsxDeclarations: Set<NodePath<t.Node>>,
  // @TODO
  // 把 templates 换成 Map 可以支持 shalow variables declared
  // 现在先用 ESLint 的 no-shalow 顶着
  templates: Map<string, t.JSXElement>,
  loopScopes: Set<string>,
  finalReturnElement: t.JSXElement,
  returnedPaths: NodePath<t.Node>[]
) {
  const bodyScope = body.scope
  body.traverse({
    JSXElement (jsxElementPath) {
      const parentNode = jsxElementPath.parent
      const parentPath = jsxElementPath.parentPath
      const isFinalReturn = jsxElementPath.getFunctionParent().isClassMethod()
      const isJSXChildren = t.isJSXElement(parentNode)
      if (!isJSXChildren) {
        let statementParent = jsxElementPath.getStatementParent()
        if (
          !(
            statementParent.isVariableDeclaration() ||
            statementParent.isExpressionStatement()
          )
        ) {
          statementParent = statementParent.findParent(
            s => s.isVariableDeclaration() || s.isExpressionStatement()
          ) as NodePath<t.Statement>
        }
        jsxDeclarations.add(statementParent)
        if (t.isVariableDeclarator(parentNode)) {
          if (statementParent) {
            const name = findIdentifierFromStatement(statementParent.node as t.VariableDeclaration)
            // setTemplate(name, path, templates)
            name && templates.set(name, jsxElementPath.node)
          }
        } else if (t.isLogicalExpression(parentNode)) {
          const { left, operator } = parentNode
          if (operator === '&&') {
            if (t.isExpression(left)) {
              newJSXIfAttr(jsxElementPath.node, left)
              parentPath.replaceWith(jsxElementPath.node)
              if (statementParent) {
                const name = findIdentifierFromStatement(statementParent.node as t.VariableDeclaration)
                setTemplate(name, jsxElementPath, templates)
                // name && templates.set(name, path.node)
              }
            }
          }
        } else if (t.isConditionalExpression(parentNode)) {
          const { test, consequent, alternate } = parentNode
          const block = buildBlockElement()
          if (t.isJSXElement(consequent) && t.isLiteral(alternate)) {
            const { value, confident } = parentPath.get('alternate').evaluate()
            if (confident && !value) {
              newJSXIfAttr(block, test)
              block.children = [ jsxElementPath.node ]
              // newJSXIfAttr(jsxElementPath.node, test)
              parentPath.replaceWith(block)
              if (statementParent) {
                const name = findIdentifierFromStatement(
                  statementParent.node as t.VariableDeclaration
                )
                setTemplate(name, jsxElementPath, templates)
                // name && templates.set(name, path.node)
              }
            }
          } else if (t.isLiteral(consequent) && t.isJSXElement(consequent)) {
            if (t.isNullLiteral(consequent)) {
              newJSXIfAttr(block, reverseBoolean(test))
              // newJSXIfAttr(jsxElementPath.node, reverseBoolean(test))
              parentPath.replaceWith(block)
              if (statementParent) {
                const name = findIdentifierFromStatement(
                  statementParent.node as t.VariableDeclaration
                )
                setTemplate(name, jsxElementPath, templates)
                // name && templates.set(name, path.node)
              }
            }
          } else if (t.isJSXElement(consequent) && t.isJSXElement(alternate)) {
            const block2 = buildBlockElement()
            block.children = [consequent]
            newJSXIfAttr(block, test)
            setJSXAttr(block2, Adapter.else)
            block2.children = [alternate]
            const parentBlock = buildBlockElement()
            parentBlock.children = [block, block2]
            parentPath.replaceWith(parentBlock)
            if (statementParent) {
              const name = findIdentifierFromStatement(
                statementParent.node as t.VariableDeclaration
              )
              setTemplate(name, jsxElementPath, templates)
            }
          } else {
            // console.log('todo')
          }
        } else if (t.isReturnStatement(parentNode)) {
          if (!isFinalReturn) {
            const caller = parentPath.findParent(p => p.isCallExpression())
            if (caller.isCallExpression()) {
              const callee = caller.node.callee
              if (
                t.isMemberExpression(callee) &&
                t.isIdentifier(callee.property) &&
                callee.property.name === 'map'
              ) {
                let ary = callee.object
                const blockStatementPath = parentPath.findParent(p => p.isBlockStatement()) as NodePath<t.BlockStatement>
                const body = blockStatementPath.node.body
                let stateToBeAssign = new Set<string>()
                for (const statement of body) {
                  if (t.isVariableDeclaration(statement)) {
                    for (const dcl of statement.declarations) {
                      if (t.isIdentifier(dcl.id)) {
                        const scope = blockStatementPath.scope
                        const stateName = scope.generateUid(LOOP_STATE)
                        stateToBeAssign.add(stateName)
                        blockStatementPath.scope.rename(dcl.id.name, stateName)
                      }
                    }
                  }
                }
                if (t.isCallExpression(ary) || isContainFunction(caller.get('callee').get('object'))) {
                  const variableName = `anonymousState_${bodyScope.generateUid()}`
                  caller.getStatementParent().insertBefore(
                    buildConstVariableDeclaration(variableName, ary)
                  )
                  ary = t.identifier(variableName)
                }
                setJSXAttr(jsxElementPath.node, Adapter.for, t.jSXExpressionContainer(ary))
                const [func] = caller.node.arguments
                if (
                  t.isFunctionExpression(func) ||
                  t.isArrowFunctionExpression(func)
                ) {
                  const [item, index] = func.params
                  if (t.isIdentifier(item)) {
                    setJSXAttr(
                      jsxElementPath.node,
                      Adapter.forItem,
                      t.stringLiteral(item.name)
                    )
                    loopScopes.add(item.name)
                  } else {
                    setJSXAttr(
                      jsxElementPath.node,
                      Adapter.forItem,
                      t.stringLiteral('__item')
                    )
                  }
                  if (t.isIdentifier(index)) {
                    setJSXAttr(
                      jsxElementPath.node,
                      Adapter.forIndex,
                      t.stringLiteral(index.name)
                    )
                    loopScopes.add(index.name)
                  }
                  caller.replaceWith(jsxElementPath.node)
                  if (statementParent) {
                    const name = findIdentifierFromStatement(
                      statementParent.node as t.VariableDeclaration
                    )
                    // setTemplate(name, path, templates)
                    name && templates.set(name, jsxElementPath.node)
                  }
                }
              }
            }
          } else {
            const ifStatement = parentPath.findParent(p => p.isIfStatement())
            const blockStatement = parentPath.findParent(p => p.isBlockStatement())
            const block = finalReturnElement || buildBlockElement()
            if (isBlockIfStatement(ifStatement, blockStatement)) {
              const { test, alternate, consequent } = ifStatement.node
              if (alternate === blockStatement.node) {
                throw 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) {
                  setJSXAttr(
                    jsxElementPath.node,
                    Adapter.elseif,
                    t.jSXExpressionContainer(test)
                  )
                } else {
                  newJSXIfAttr(jsxElementPath.node, test)
                }
              }
            } else if (block.children.length !== 0) {
              setJSXAttr(jsxElementPath.node, Adapter.else)
            }
            block.children.push(jsxElementPath.node)
            finalReturnElement = block
            returnedPaths.push(parentPath)
          }
        } else if (t.isArrowFunctionExpression(parentNode)) {
          //
        } else if (t.isAssignmentExpression(parentNode)) {
          if (t.isIdentifier(parentNode.left)) {
            const name = parentNode.left.name
            const bindingNode = bodyScope.getOwnBinding(name)!.path.node
            const block = templates.get(name) || buildBlockElement()
            if (isEmptyDeclarator(bindingNode)) {
              const ifStatement = parentPath.findParent(p => p.isIfStatement())
              const blockStatement = parentPath.findParent(p =>
                p.isBlockStatement()
              )
              if (isBlockIfStatement(ifStatement, blockStatement)) {
                const { test, alternate, consequent } = ifStatement.node
                if (alternate === blockStatement.node) {
                  setJSXAttr(jsxElementPath.node, Adapter.else)
                } else if (consequent === blockStatement.node) {
                  const parentIfStatement = ifStatement.findParent(p =>
                    p.isIfStatement()
                  ) as NodePath<t.IfStatement>
                  if (parentIfStatement && parentIfStatement.get('alternate') === ifStatement) {
                    setJSXAttr(
                      jsxElementPath.node,
                      Adapter.elseif,
                      t.jSXExpressionContainer(test)
                    )
                  } else {
                    if (parentIfStatement) {
                      newJSXIfAttr(block, parentIfStatement.node.test)
                    }
                    newJSXIfAttr(jsxElementPath.node, test)
                  }
                }
                block.children.push(jsxElementPath.node)
                // setTemplate(name, path, templates)
                name && templates.set(name, block)
              }
            } else {
              throw codeFrameError(
                jsxElementPath.node.loc,
                '请将 JSX 赋值表达式初始化为 null,然后再进行 if 条件表达式赋值。'
              )
            }
          }
        } else if (!t.isJSXElement(parentNode)) {
          // throwError(path, '考虑只对 JSX 元素赋值一次。')
        }
      }
    }
  })
}