Example #1
0
export default declare(api => {
  api.assertVersion(7);

  return {
    name: "transform-react-jsx-compat",

    manipulateOptions(opts, parserOpts) {
      parserOpts.plugins.push("jsx");
    },

    visitor: helper({
      pre(state) {
        state.callee = state.tagExpr;
      },

      post(state) {
        if (t.react.isCompatTag(state.tagName)) {
          state.call = t.callExpression(
            t.memberExpression(
              t.memberExpression(t.identifier("React"), t.identifier("DOM")),
              state.tagExpr,
              t.isLiteral(state.tagExpr),
            ),
            state.args,
          );
        }
      },
      compat: true,
    }),
  };
});
Example #2
0
export default declare(api => {
  api.assertVersion(7);

  function hasRefOrSpread(attrs) {
    for (let i = 0; i < attrs.length; i++) {
      const attr = attrs[i];
      if (t.isJSXSpreadAttribute(attr)) return true;
      if (isJSXAttributeOfName(attr, "ref")) return true;
    }
    return false;
  }

  function isJSXAttributeOfName(attr, name) {
    return (
      t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name, { name: name })
    );
  }

  const visitor = helper({
    filter(node) {
      return (
        // Regular JSX nodes have an `openingElement`. JSX fragments, however, don't have an
        // `openingElement` which causes `node.openingElement.attributes` to throw.
        node.openingElement && !hasRefOrSpread(node.openingElement.attributes)
      );
    },
    pre(state) {
      const tagName = state.tagName;
      const args = state.args;
      if (t.react.isCompatTag(tagName)) {
        args.push(t.stringLiteral(tagName));
      } else {
        args.push(state.tagExpr);
      }
    },
    post(state, pass) {
      state.callee = pass.addHelper("jsx");
      // NOTE: The arguments passed to the "jsx" helper are:
      //   (element, props, key, ...children) or (element, props)
      // The argument generated by the helper are:
      //   (element, { ...props, key }, ...children)

      const props = state.args[1];
      let hasKey = false;
      if (t.isObjectExpression(props)) {
        const keyIndex = props.properties.findIndex(prop =>
          t.isIdentifier(prop.key, { name: "key" }),
        );
        if (keyIndex > -1) {
          state.args.splice(2, 0, props.properties[keyIndex].value);
          props.properties.splice(keyIndex, 1);
          hasKey = true;
        }
      } else if (t.isNullLiteral(props)) {
        state.args.splice(1, 1, t.objectExpression([]));
      }

      if (!hasKey && state.args.length > 2) {
        state.args.splice(2, 0, t.unaryExpression("void", t.numericLiteral(0)));
      }
    },
  });
  return {
    name: "transform-react-inline-elements",
    visitor,
  };
});
Example #3
0
export default function(api, options) {
  const THROW_IF_NAMESPACE =
    options.throwIfNamespace === undefined ? true : !!options.throwIfNamespace;

  const PRAGMA_DEFAULT = options.pragma || "React.createElement";
  const PRAGMA_FRAG_DEFAULT = options.pragmaFrag || "React.Fragment";

  const JSX_ANNOTATION_REGEX = /\*?\s*@jsx\s+([^\s]+)/;
  const JSX_FRAG_ANNOTATION_REGEX = /\*?\s*@jsxFrag\s+([^\s]+)/;

  // returns a closure that returns an identifier or memberExpression node
  // based on the given id
  const createIdentifierParser = (id: string) => () => {
    return id
      .split(".")
      .map(name => t.identifier(name))
      .reduce((object, property) => t.memberExpression(object, property));
  };

  const visitor = helper({
    pre(state) {
      const tagName = state.tagName;
      const args = state.args;
      if (t.react.isCompatTag(tagName)) {
        args.push(t.stringLiteral(tagName));
      } else {
        args.push(state.tagExpr);
      }
    },

    post(state, pass) {
      state.callee = pass.get("jsxIdentifier")();
    },

    throwIfNamespace: THROW_IF_NAMESPACE,
  });

  visitor.Program = {
    enter(path, state) {
      const { file } = state;

      let pragma = PRAGMA_DEFAULT;
      let pragmaFrag = PRAGMA_FRAG_DEFAULT;
      let pragmaSet = !!options.pragma;
      let pragmaFragSet = !!options.pragmaFrag;

      for (const comment of (file.ast.comments: Array<Object>)) {
        const jsxMatches = JSX_ANNOTATION_REGEX.exec(comment.value);
        if (jsxMatches) {
          pragma = jsxMatches[1];
          pragmaSet = true;
        }
        const jsxFragMatches = JSX_FRAG_ANNOTATION_REGEX.exec(comment.value);
        if (jsxFragMatches) {
          pragmaFrag = jsxFragMatches[1];
          pragmaFragSet = true;
        }
      }

      state.set("jsxIdentifier", createIdentifierParser(pragma));
      state.set("jsxFragIdentifier", createIdentifierParser(pragmaFrag));
      state.set("usedFragment", false);
      state.set("pragmaSet", pragmaSet);
      state.set("pragmaFragSet", pragmaFragSet);
    },
    exit(path, state) {
      if (
        state.get("pragmaSet") &&
        state.get("usedFragment") &&
        !state.get("pragmaFragSet")
      ) {
        throw new Error(
          "transform-react-jsx: pragma has been set but " +
            "pragmafrag has not been set",
        );
      }
    },
  };

  visitor.JSXAttribute = function(path) {
    if (t.isJSXElement(path.node.value)) {
      path.node.value = t.jsxExpressionContainer(path.node.value);
    }
  };

  return {
    inherits: jsx,
    visitor,
  };
}