Front knowledge

  • AST spec
  • Babel plug-in development manual

The source code parsing

The source code

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; }},},},}; });Copy the code

use

The official instructions

Look at UT, the key of the object, if it is a keyword, will be from. Form to [] form

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

// output.js
test["catch"];
test["catch"].foo;
test["catch"];
test["catch"].foo;
Copy the code

parsing

According to the Babel plug-in development manual, the visitor in the plug-in processes MemberExpression. The MemberExpression in the AST Spec is described as follows:

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

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.

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

The exit method takes a responsive Path object with attributes such as parent and node. Path.node. property gets the property value of an AST node

T.i sIdentifier(prop) equivalent to Node.property. type === ‘identifier’

IsValidES3Identifier is used to verify the validity of a key. The code is as follows:

/** * 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 (typeofname ! = ="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 {
  returnisValidIdentifier(name) && ! RESERVED_WORDS_ES3_ONLY.has(name); }Copy the code

node.property = t.stringLiteral(prop.name); Change PrivateName to Expression.

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

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

AST tree comparison (stripped of parsed location information)

// 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"
}
Copy the code
// 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"
}
Copy the code

Change parts

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

// test["catch"].foo
"property": {
  "type": "Literal",
  "value": "catch",
  "raw": "\"catch\""
},
"computed": true,
Copy the code