Repository: glayzzle/php-parser (js)

Issue:
Curly braces in encapsed string, again
When working on prettier support for PHP (#100) i noticed something odd about curly braces in encapsed strings:
```php
"Hello {$foo}";
```
yields
```javascript
[ String {
            kind: 'string',
            loc: 
             Location {
               source: null,
               start: Position { line: 1, column: 1, offset: 1 },
               end: Position { line: 1, column: 7, offset: 7 } },
            value: 'Hello ',
            isDoubleQuote: false },
          Variable {
            kind: 'variable',
            loc: 
             Location {
               source: null,
               start: Position { line: 1, column: 8, offset: 8 },
               end: Position { line: 1, column: 12, offset: 12 } },
            name: 'foo',
            byref: false,
            curly: false } ],
```
But 
```php
"Hello ${foo}";
```
yields 
```javascript
[ String {
            kind: 'string',
            loc: 
             Location {
               source: null,
               start: Position { line: 1, column: 1, offset: 1 },
               end: Position { line: 1, column: 7, offset: 7 } },
            value: 'Hello ',
            isDoubleQuote: false },
          Variable {
            kind: 'variable',
            loc: 
             Location {
               source: null,
               start: Position { line: 1, column: 7, offset: 7 },
               end: Position { line: 1, column: 13, offset: 13 } },
            name: 'foo',
            byref: false,
            curly: true } ],
```
In the first case, the information about the surrounding curly braces seems to be lost, which makes it hard to re-print the node the way it was found. 
What do you think about this? If there is no semantic difference between the two different styles, we might get away with setting `curly:true` in both cases?

Pull request description:
Enhance AST representation of curlies / encapsed strings
Fixes #101 

Patch:
diff --git a/src/parser/scalar.js b/src/parser/scalar.js
index 0a9ab134..82709582 100644
--- a/src/parser/scalar.js
+++ b/src/parser/scalar.js
@@ -199,7 +199,7 @@ module.exports = {
           this.expect("]") && this.next();
           name = node(name, offset);
         } else {
-          name = varName;
+          name = this.node("constref")(varName);
         }
       } else {
         name = this.read_expr();
@@ -210,6 +210,9 @@ module.exports = {
       // expression
       // https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y#L1246
       result = this.next().read_variable(false, false, false);
+      if (result.kind === "variable") {
+        result.curly = true;
+      }
       this.expect("}") && this.next();
     } else if (this.token === this.tok.T_VARIABLE) {
       // plain variable


Test patch:
diff --git a/test/stringTests.js b/test/stringTests.js
index bbc361c3..b21a0851 100644
--- a/test/stringTests.js
+++ b/test/stringTests.js
@@ -1,28 +1,41 @@
 var parser = require("../src/index");
 
 describe("Test strings", function() {
+  it("fix #101", function() {
+    let ast = parser.parseEval('"encapsed {$var}";');
+    ast.children[0].value[1].kind.should.be.exactly("variable");
+    ast.children[0].value[1].curly.should.be.true();
+    ast = parser.parseEval('"encapsed {$arr[0]}";');
+    ast.children[0].value[1].kind.should.be.exactly("offsetlookup");
+    ast = parser.parseEval('"encapsed ${var}";');
+    ast.children[0].value[1].name.kind.should.be.exactly("constref");
+  });
   it("fix #124", function() {
-    const ast = parser.parseEval("$string = \"He drank some $juices[koolaid1] juice.\";");
+    const ast = parser.parseEval(
+      '$string = "He drank some $juices[koolaid1] juice.";'
+    );
     const text = ast.children[0].right;
-    text.kind.should.be.exactly('encapsed');
+    text.kind.should.be.exactly("encapsed");
     text.value.length.should.be.exactly(3);
     const offset = text.value[1];
-    offset.kind.should.be.exactly('offsetlookup');
-    offset.what.name.should.be.exactly('juices');
-    offset.offset.kind.should.be.exactly('constref');
-    offset.offset.name.should.be.exactly('koolaid1');
+    offset.kind.should.be.exactly("offsetlookup");
+    offset.what.name.should.be.exactly("juices");
+    offset.offset.kind.should.be.exactly("constref");
+    offset.offset.name.should.be.exactly("koolaid1");
   });
-  it('fix #123', function() {
+  it("fix #123", function() {
     const ast = parser.parseEval(
-      "$string = \'Avoid converting \n chars, but \\' or \\\\ is ok.\';"
+      "$string = 'Avoid converting \n chars, but \\' or \\\\ is ok.';"
     );
     const text = ast.children[0].right;
-    text.value.should.be.exactly('Avoid converting \n chars, but \' or \\ is ok.');
+    text.value.should.be.exactly(
+      "Avoid converting \n chars, but ' or \\ is ok."
+    );
   });
-  it('implement #116', function() {
-    const ast = parser.parseEval("$a = \"foo\\nbar\";");
+  it("implement #116", function() {
+    const ast = parser.parseEval('$a = "foo\\nbar";');
     const text = ast.children[0].right;
-    text.raw.should.be.exactly("\"foo\\nbar\"");
+    text.raw.should.be.exactly('"foo\\nbar"');
   });
   it("...", function() {
     var ast = parser.parseEval("$a = b'\\t\\ra';");
@@ -36,70 +49,71 @@ describe("Test strings", function() {
   it("check infinite on $", function() {
     var ast = parser.parseEval('echo "$', {
       parser: { suppressErrors: true, debug: false },
-      lexer: { debug: false },
+      lexer: { debug: false }
     });
-    ast.children[0].arguments[0].value[0].kind.should.be.exactly('string');
-    ast.children[0].arguments[0].value[0].value.should.be.exactly('$');
+    ast.children[0].arguments[0].value[0].kind.should.be.exactly("string");
+    ast.children[0].arguments[0].value[0].value.should.be.exactly("$");
 
     // @todo ...
-    ast = parser.parseEval('echo `$', {
+    ast = parser.parseEval("echo `$", {
       parser: { suppressErrors: true, debug: false },
-      lexer: { debug: false },
+      lexer: { debug: false }
     });
-    ast = parser.parseEval('echo ` -> $', {
+    ast = parser.parseEval("echo ` -> $", {
       parser: { suppressErrors: true, debug: false },
-      lexer: { debug: false },
+      lexer: { debug: false }
     });
-
   });
   it("check infinite on {", function() {
     var ast = parser.parseEval('echo "{', {
       parser: { suppressErrors: true, debug: false },
-      lexer: { debug: false },
+      lexer: { debug: false }
     });
-    ast.children[0].arguments[0].value[0].kind.should.be.exactly('string');
-    ast.children[0].arguments[0].value[0].value.should.be.exactly('{');
-    ast = parser.parseEval('echo `{', {
+    ast.children[0].arguments[0].value[0].kind.should.be.exactly("string");
+    ast.children[0].arguments[0].value[0].value.should.be.exactly("{");
+    ast = parser.parseEval("echo `{", {
       parser: { suppressErrors: true, debug: false },
-      lexer: { debug: false },
+      lexer: { debug: false }
     });
-    ast = parser.parseEval('echo ` -> {', {
+    ast = parser.parseEval("echo ` -> {", {
       parser: { suppressErrors: true, debug: false },
-      lexer: { debug: false },
+      lexer: { debug: false }
     });
   });
   it("check infinite on ${", function() {
     var ast = parser.parseEval('echo "${', {
       parser: { suppressErrors: true, debug: false },
-      lexer: { debug: false },
+      lexer: { debug: false }
     });
-    ast.children[0].arguments[0].value[0].kind.should.be.exactly('variable');
-    ast = parser.parseEval('echo `${', {
+    ast.children[0].arguments[0].value[0].kind.should.be.exactly("variable");
+    ast = parser.parseEval("echo `${", {
       parser: { suppressErrors: true, debug: false },
-      lexer: { debug: false },
+      lexer: { debug: false }
     });
-    ast = parser.parseEval('echo ` -> ${', {
+    ast = parser.parseEval("echo ` -> ${", {
       parser: { suppressErrors: true, debug: false },
-      lexer: { debug: false },
+      lexer: { debug: false }
     });
   });
   it("check infinite on {$", function() {
     var ast = parser.parseEval('echo "{$', {
       parser: { suppressErrors: true, debug: false },
-      lexer: { debug: false },
+      lexer: { debug: false }
     });
-    ast.children[0].arguments[0].value[0].kind.should.be.exactly('variable');
-    ast = parser.parseEval('echo `{$', {
+    ast.children[0].arguments[0].value[0].kind.should.be.exactly("variable");
+    ast = parser.parseEval("echo `{$", {
       parser: { suppressErrors: true, debug: false },
-      lexer: { debug: false },
+      lexer: { debug: false }
     });
-    ast = parser.parseEval('echo ` -> {$', {
+    ast = parser.parseEval("echo ` -> {$", {
       parser: { suppressErrors: true, debug: false },
-      lexer: { debug: false },
+      lexer: { debug: false }
     });
   });
   it("binary cast", function() {
-    var ast = parser.parseEval('echo (binary)"\\colors[1] contains >$colors[1]<\\n";');
+    var ast = parser.parseEval(
+      'echo (binary)"\\colors[1] contains >$colors[1]<\\n";'
+    );
     // @todo console.log(ast.children[0].arguments[0]);
   });
   it("...", function() {
@@ -150,7 +164,7 @@ describe("Test strings", function() {
     arg1.value[2].value.should.be.exactly("->name !");
     // test the varname
     arg1.value[1].kind.should.be.exactly("variable");
-    arg1.value[1].name.should.be.exactly("obj");
+    arg1.value[1].name.name.should.be.exactly("obj");
     arg1.value[1].curly.should.be.exactly(true);
   });
   it("Encapsed variable / curly constant", function() {