Repository: glayzzle/php-parser (js)

Issue:
Support array destructuring
```php
[$id1, $name1] = $data[0];
```

```php
["id" => $id1, "name" => $name1] = $data[0];
```

Ref: http://php.net/manual/en/migration71.new-features.php

Pull request description:
Add support for array destructuring
I finally dared (and found the time) to take a closer look at the parser :smile: I'm curious to hear your feedback!

Fixes #121 

Patch:
diff --git a/src/parser/expr.js b/src/parser/expr.js
index c294d1ce..93799521 100644
--- a/src/parser/expr.js
+++ b/src/parser/expr.js
@@ -121,18 +121,7 @@ module.exports = {
       const node = this.node("parenthesis");
       expr = this.next().read_expr();
       this.expect(")") && this.next();
-      expr = node(expr);
-      // handle dereferencable
-      if (this.token === this.tok.T_OBJECT_OPERATOR) {
-        return this.recursive_variable_chain_scan(expr, false);
-      } else if (this.token === this.tok.T_CURLY_OPEN || this.token === "[") {
-        return this.read_dereferencable(expr);
-      } else if (this.token === "(") {
-        // https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y#L1118
-        return this.node("call")(expr, this.read_function_argument_list());
-      } else {
-        return expr;
-      }
+      return this.handleDereferencable(node(expr));
     }
 
     if (this.token === "`") {
@@ -185,6 +174,15 @@ module.exports = {
       }
     }
 
+    if (this.token === "[") {
+      expr = this.read_array();
+      if (this.token === "=") {
+        return this.node("assign")(expr, this.next().read_expr(), "=");
+      } else {
+        return this.handleDereferencable(expr);
+      }
+    }
+
     if (this.token === this.tok.T_CLONE)
       return this.node("clone")(this.next().read_expr());
 
@@ -417,19 +415,7 @@ module.exports = {
       }
     } else if (this.is("SCALAR")) {
       expr = this.read_scalar();
-      // handle dereferencable
-      while (this.token !== this.EOF) {
-        if (this.token === this.tok.T_OBJECT_OPERATOR) {
-          expr = this.recursive_variable_chain_scan(expr, false);
-        } else if (this.token === this.tok.T_CURLY_OPEN || this.token === "[") {
-          expr = this.read_dereferencable(expr);
-        } else if (this.token === "(") {
-          // https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y#L1118
-          expr = this.node("call")(expr, this.read_function_argument_list());
-        } else {
-          return expr;
-        }
-      }
+      return this.handleDereferencable(expr);
     } else {
       this.error("EXPR");
       this.next();
@@ -521,5 +507,21 @@ module.exports = {
       result = ["key", result, this.next().read_expr_item()];
     }
     return result;
+  },
+
+  handleDereferencable: function(expr) {
+    while (this.token !== this.EOF) {
+      if (this.token === this.tok.T_OBJECT_OPERATOR) {
+        expr = this.recursive_variable_chain_scan(expr, false);
+      } else if (this.token === this.tok.T_CURLY_OPEN || this.token === "[") {
+        expr = this.read_dereferencable(expr);
+      } else if (this.token === "(") {
+        // https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y#L1118
+        expr = this.node("call")(expr, this.read_function_argument_list());
+      } else {
+        return expr;
+      }
+    }
+    return expr;
   }
 };


Test patch:
diff --git a/test/variableTests.js b/test/variableTests.js
index 200f3052..b97e7654 100644
--- a/test/variableTests.js
+++ b/test/variableTests.js
@@ -1,6 +1,16 @@
 var parser = require("./main");
 
 describe("Test variables", function() {
+  describe("array destructuring", function() {
+    // Get result from parser
+    var ast = parser.parseEval("[$id1, $name1] = $data[0];");
+    it("should be assign with array", function() {
+      ast.children[0].kind.should.be.exactly("assign");
+      ast.children[0].left.kind.should.be.exactly("array");
+      ast.children[0].operator.should.be.exactly("=");
+    });
+  });
+
   describe("Default variables", function() {
     var ast = parser.parseEval(
       ['$a = "foo";', "$b = &$c;", "$a->b = true;"].join("\n")
@@ -197,11 +207,9 @@ describe("Test variables", function() {
       astErr.errors[0].line.should.be.exactly(1);
       astErr.errors[0].message.should.be.exactly(msg);
     });
-    
+
     it("should fail on property lookup on static lookup", function() {
-      var astErr = parser.parseEval([
-        "$this->foo::bar->baz;"
-      ].join('\n'), {
+      var astErr = parser.parseEval(["$this->foo::bar->baz;"].join("\n"), {
         parser: {
           suppressErrors: true
         }
@@ -213,27 +221,27 @@ describe("Test variables", function() {
       astErr.errors[0].line.should.be.exactly(1);
       astErr.errors[0].message.should.be.exactly(msg);
     });
-    
-    it('should fail $foo->bar::!', function() {
-      var errAst = parser.parseEval('$foo->bar::!', {
+
+    it("should fail $foo->bar::!", function() {
+      var errAst = parser.parseEval("$foo->bar::!", {
         parser: {
           suppressErrors: true
         }
       });
       errAst.errors.length.should.be.exactly(1);
-      errAst.errors[0].token.should.be.exactly('\'!\'');
-      errAst.children[0].kind.should.be.exactly('staticlookup');
-      errAst.children[0].offset.name.should.be.exactly('!');
+      errAst.errors[0].token.should.be.exactly("'!'");
+      errAst.children[0].kind.should.be.exactly("staticlookup");
+      errAst.children[0].offset.name.should.be.exactly("!");
     });
 
-    it('should fail foo::bar::baz', function() {
-      var errAst = parser.parseEval('foo::bar::baz', {
+    it("should fail foo::bar::baz", function() {
+      var errAst = parser.parseEval("foo::bar::baz", {
         parser: {
           suppressErrors: true
         }
       });
       errAst.errors.length.should.be.exactly(1);
-      errAst.children[0].kind.should.be.exactly('staticlookup');
+      errAst.children[0].kind.should.be.exactly("staticlookup");
     });
   });
 });