Search code examples
javascriptabstract-syntax-tree

What are all the ways you can encounter an EmptyStatement in a JavaScript AST?


I am writing a JS transpiler and need to know what I will encounter. I have handled almost every edge case except for the obscure EmptyStatement. One place I encounter it is here:

for (arrL = arr.length; arrL--; arr[arrL] *= baseIn);

The AST looks like this:

{
  "type": "Program",
  "body": [
    {
      "type": "BlockStatement",
      "body": [
        {
          "type": "AssignmentExpression",
          "left": {
            "type": "Identifier",
            "start": 4014,
            "end": 4018,
            "name": "arrL"
          },
          "right": {
            "type": "MemberExpression",
            "object": {
              "type": "Identifier",
              "start": 4021,
              "end": 4024,
              "name": "arr"
            },
            "property": {
              "type": "Identifier",
              "start": 4025,
              "end": 4031,
              "name": "length"
            },
            "computed": false
          },
          "operator": "="
        },
        {
          "type": "WhileStatement",
          "test": {
            "type": "Literal",
            "value": true,
            "raw": "true"
          },
          "body": {
            "type": "BlockStatement",
            "body": [
              {
                "type": "IfStatement",
                "test": {
                  "type": "UpdateExpression",
                  "argument": {
                    "type": "Identifier",
                    "start": 4033,
                    "end": 4037,
                    "name": "arrL"
                  },
                  "operator": "--",
                  "prefix": false
                },
                "consequent": {
                  "type": "BlockStatement",
                  "body": [
                    {
                      "type": "EmptyStatement",
                      "start": 4061,
                      "end": 4062
                    },
                    {
                      "type": "AssignmentExpression",
                      "left": {
                        "type": "MemberExpression",
                        "object": {
                          "type": "Identifier",
                          "start": 4041,
                          "end": 4044,
                          "name": "arr"
                        },
                        "property": {
                          "type": "Identifier",
                          "start": 4045,
                          "end": 4049,
                          "name": "arrL"
                        },
                        "computed": true
                      },
                      "right": {
                        "type": "Identifier",
                        "start": 4054,
                        "end": 4060,
                        "name": "baseIn"
                      },
                      "operator": "*="
                    }
                  ]
                },
                "alternate": {
                  "type": "BlockStatement",
                  "body": [
                    {
                      "type": "BreakStatement"
                    }
                  ]
                }
              }
            ]
          }
        }
      ]
    }
  ]
}

I don't even know what that EmptyStatement is referencing yet haha. What are the other cases where you run into EmptyStatement?


Solution

  • If you look closely at that for statement, you'll see that it's body consists only of ;. That's an empty statement. You'd get the same effect with, say,

    if (a--);
    

    although many style guides discourage that. {} is a lot clearer, imho. That's an empty block, not an empty statement. {;} would be a block consisting only of an empty statement, which is also pretty rare, but you might find something like :

    if (debugging) {
        /* Commented out for now */ ;
    }
    

    So, what's an empty statement? It's a statement with nothing but a semicolon terminator. Empty, as it says.

    It's interesting that the for statement has already been desugared, which might be confusing. But it's correct; for(init; test; advance) body; is semantically equivalent to

    {
      init;
      while(true) {
        if (test) {
          body;
          advance;
        }
        else 
          break;
      }
    }
    

    Apparently, even though it desugared the for statement, it preserved the empty body. That might be in order to have a place to hang the line number, or it might just be that it was easier to leave it at that point in the parse. The curious transformation of an (implicit) while (test) { ... } into while (true) { if (test) { ... } else break; } is probably to simplify decomposition into basic blocks, or to enable a standard optimization which reorders the code. Just guesses, though.