Babel的奇妙冒险 @babel/plugin-transform-member-expression-literals

时间:2021-2-20 作者:admin

前置知识

源码解析

源代码

import { declare } from "@babel/helper-plugin-utils";
import { types as t } from "@babel/core";

export default declare(api => {
  api.assertVersion(7);

  return {
    name: "transform-member-expression-literals",

    visitor: {
      MemberExpression: {
        exit({ node }) {
          const prop = node.property;
          if (
            !node.computed &&
            t.isIdentifier(prop) &&
            !t.isValidES3Identifier(prop.name)
          ) {
            // foo.default -> foo["default"]
            node.property = t.stringLiteral(prop.name);
            node.computed = true;
          }
        },
      },
    },
  };
});

用途

官方说明

看 UT,对象的key如果是关键字,会从.形式转为[]形式

// input.js
test.catch;
test.catch.foo;
test["catch"];
test["catch"].foo;

// output.js
test["catch"];
test["catch"].foo;
test["catch"];
test["catch"].foo;

解析

通过Babel 插件开发手册,可知插件中的访问器处理 MemberExpression,AST spec中 MemberExpression 说明如下:

interface MemberExpression <: Expression, Pattern {
  type: "MemberExpression";
  object: Expression | Super;
  property: Expression | PrivateName;
  computed: boolean;
}

A member expression. If computed is true, the node corresponds to a computed (a[b]) member expression and property is an Expression. If computed is false, the node corresponds to a static (a.b) member expression and property is an Identifier or a PrivateName.

意思是:成员表达式。如果 computed 为 true,则节点对应于计算的(a[b])成员表达式,而 property 是表达式。如果 computed 为 false,则节点对应于静态(a.b)成员表达式,并且属性是标识符或 PrivateName。

exit 方法的参数是一个响应式的 Path 对象, 包含 parent, node 等属性。path.node.property得到一个AST节点的属性值

t.isIdentifier(prop) 相当于 node.property.type === 'identifier'

isValidES3Identifier 主要验证下 key 合法性,部分代码如下:

/**
 * Check if the input `name` is a valid identifier name
 * and isn't a reserved word.
 */
export default function isValidIdentifier(
  name: string,
  reserved: boolean = true,
): boolean {
  if (typeof name !== "string") return false;

  if (reserved) {
    if (isKeyword(name) || isStrictReservedWord(name)) {
      return false;
    } else if (name === "await") {
      // invalid in module, valid in script; better be safe (see #4952)
      return false;
    }
  }

  return isIdentifierName(name);
}

const RESERVED_WORDS_ES3_ONLY: Set<string> = new Set([
  "abstract",
  "boolean",
  "byte",
  "char",
  "double",
  "enum",
  "final",
  "float",
  "goto",
  "implements",
  "int",
  "interface",
  "long",
  "native",
  "package",
  "private",
  "protected",
  "public",
  "short",
  "static",
  "synchronized",
  "throws",
  "transient",
  "volatile",
]);

/**
 * Check if the input `name` is a valid identifier name according to the ES3 specification.
 *
 * Additional ES3 reserved words are
 */
export default function isValidES3Identifier(name: string): boolean {
  return isValidIdentifier(name) && !RESERVED_WORDS_ES3_ONLY.has(name);
}

node.property = t.stringLiteral(prop.name); 作用是把 PrivateName 改为 Expression。

interface PrivateName <: Node {
  type: "PrivateName";
  id: Identifier;
}
interface Literal <: Expression { }

interface StringLiteral <: Literal {
  type: "StringLiteral";
  value: string;
}

AST树对比(去掉了解析的位置信息)

// test.catch.foo;
{
  "type": "Program",
  "body": [
    {
      "type": "ExpressionStatement",
      "expression": {
        "type": "MemberExpression",
        "object": {
          "type": "MemberExpression",
          "object": {
            "type": "Identifier",
            "name": "test"
          },
          "property": {
            "type": "Identifier",
            "name": "catch"
          },
          "computed": false,
          "optional": false
        },
        "property": {
          "type": "Identifier",
          "name": "foo"
        },
        "computed": false,
        "optional": false
      }
    }
  ],
  "sourceType": "module"
}
// test["catch"].foo
{
  "type": "Program",
  "body": [
    {
      "type": "ExpressionStatement",
      "expression": {
        "type": "MemberExpression",
        "object": {
          "type": "MemberExpression",
          "object": {
            "type": "Identifier",
            "name": "test"
          },
          "property": {
            "type": "Literal",
            "start": 5,
            "end": 12,
            "value": "catch",
            "raw": "\"catch\""
          },
          "computed": true,
          "optional": false
        },
        "property": {
          "type": "Identifier",
          "name": "foo"
        },
        "computed": false,
        "optional": false
      }
    }
  ],
  "sourceType": "module"
}

变动部分

// test.catch.foo;
"property": {
  "type": "Identifier",
  "name": "catch"
},
"computed": false,

// test["catch"].foo
"property": {
  "type": "Literal",
  "value": "catch",
  "raw": "\"catch\""
},
"computed": true,
声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。