前置知识
源码解析
源代码
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,